Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADD] attachment_db_by_checksum #239

Merged
merged 6 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions attachment_db_by_checksum/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
================================
DB attachments saved by checksum
================================

.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstorage-lightgray.png?logo=github
:target: https://github.com/OCA/storage/tree/14.0/attachment_db_by_checksum
:alt: OCA/storage
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/storage-14-0/storage-14-0-attachment_db_by_checksum
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/275/14.0
:alt: Try me on Runbot

|badge1| |badge2| |badge3| |badge4| |badge5|

Allow to identify database attachments through their hash, avoiding duplicates.

This is typically useful when you want to save attachments to database but you want to save space avoiding to write the same content in several attachments (think of email attachments, for example, or any file uploaded more than once).

**Table of contents**

.. contents::
:local:

Configuration
=============

Set system parameter ``ir_attachment.location`` to ``hashed_db`` to activate saving by checksum.

Run ``force_storage``, method of ``ir.attachment``, to move existing attachments.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/storage/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/storage/issues/new?body=module:%20attachment_db_by_checksum%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
~~~~~~~

* TAKOBI

Contributors
~~~~~~~~~~~~

* `TAKOBI <https://takobi.online>`_:

* Lorenzo Battistini

Maintainers
~~~~~~~~~~~

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

.. |maintainer-eLBati| image:: https://github.com/eLBati.png?size=40px
:target: https://github.com/eLBati
:alt: eLBati

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-eLBati|

This module is part of the `OCA/storage <https://github.com/OCA/storage/tree/14.0/attachment_db_by_checksum>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
3 changes: 3 additions & 0 deletions attachment_db_by_checksum/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).

from . import models
21 changes: 21 additions & 0 deletions attachment_db_by_checksum/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2021 Lorenzo Battistini @ TAKOBI
# Copyright 2023 Simone Rubino - TAKOBI
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
{
"name": "DB attachments saved by checksum",
"summary": "Allow to identify database attachments through their hash, avoiding duplicates",
"version": "14.0.1.0.0",
"category": "Storage",
"website": "https://github.com/OCA/storage",
"author": "TAKOBI, Odoo Community Association (OCA)",
"maintainers": [
"eLBati",
],
"license": "LGPL-3",
"depends": [
"base",
],
"data": [
"security/ir.model.access.csv",
],
}
4 changes: 4 additions & 0 deletions attachment_db_by_checksum/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).

from . import ir_attachment_content
from . import ir_attachment
136 changes: 136 additions & 0 deletions attachment_db_by_checksum/models/ir_attachment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Copyright 2023 Simone Rubino - TAKOBI
# License LGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

import logging

from odoo import _, api, models
from odoo.exceptions import AccessError
from odoo.osv import expression

_logger = logging.getLogger(__name__)

HASHED_STORAGE_PARAMETER = "hashed_db"


class Attachment(models.Model):
_inherit = "ir.attachment"

@api.model
def _file_write_by_checksum(self, bin_value, checksum):
"""Store attachment content in `Attachment content by hash`."""
fname, full_path = self._get_path(bin_value, checksum)
attachment_content = self.env["ir.attachment.content"].search_by_checksum(fname)
if not attachment_content:
self.env["ir.attachment.content"].create(
{
"checksum": fname,
"db_datas": bin_value,
}
)
return fname

@api.model
def _file_write(self, bin_value, checksum):
location = self._storage()
if location == HASHED_STORAGE_PARAMETER:
return self._file_write_by_checksum(bin_value, checksum)
return super()._file_write(bin_value, checksum)

@api.model
def _file_read_by_checksum(self, fname):
"""Read attachment content from `Attachment content by hash`."""
attachment_content = self.env["ir.attachment.content"].search_by_checksum(fname)
if attachment_content:
bin_value = attachment_content.db_datas
else:
# Fallback on standard behavior
_logger.debug("File %s not found" % fname)
bin_value = super()._file_read(fname)
return bin_value

@api.model
def _file_read(self, fname):
location = self._storage()
if location == HASHED_STORAGE_PARAMETER:
return self._file_read_by_checksum(fname)
return super()._file_read(fname)

@api.model
def _get_all_attachments_by_checksum_domain(self, fname=None):
"""Get domain for finding all the attachments.

If `checksum` is provided,
get domain for finding all the attachments having checksum `checksum`.
"""
# trick to get every attachment, see _search method of ir.attachment
domain = [
("id", "!=", 0),
]
if fname is not None:
checksum_domain = [
("store_fname", "=", fname),
]
domain = expression.AND(
[
domain,
checksum_domain,
]
)
return domain

@api.model
def _get_all_attachments_by_checksum(self, fname=None):
"""Get all attachments.

If `checksum` is provided,
get all the attachments having checksum `checksum`.
"""
domain = self._get_all_attachments_by_checksum_domain(fname)
invisible_menu_context = {
"ir.ui.menu.full_list": True,
}
attachments = self.with_context(**invisible_menu_context).search(domain)
return attachments

@api.model
def _file_delete_by_checksum(self, fname):
"""Delete attachment content in `Attachment content by hash`."""
attachments = self._get_all_attachments_by_checksum(fname=fname)
if not attachments:
attachment_content = self.env["ir.attachment.content"].search_by_checksum(

Check warning on line 100 in attachment_db_by_checksum/models/ir_attachment.py

View check run for this annotation

Codecov / codecov/patch

attachment_db_by_checksum/models/ir_attachment.py#L100

Added line #L100 was not covered by tests
fname
)
attachment_content.unlink()

Check warning on line 103 in attachment_db_by_checksum/models/ir_attachment.py

View check run for this annotation

Codecov / codecov/patch

attachment_db_by_checksum/models/ir_attachment.py#L103

Added line #L103 was not covered by tests

@api.model
def _file_delete(self, fname):
location = self._storage()
if location == HASHED_STORAGE_PARAMETER:
self._file_delete_by_checksum(fname)
return super()._file_delete(fname)

@api.model
def force_storage_by_checksum(self):
"""Copy all the attachments to `Attachment content by hash`."""
if not self.env.is_admin():
raise AccessError(_("Only administrators can execute this action."))

Check warning on line 116 in attachment_db_by_checksum/models/ir_attachment.py

View check run for this annotation

Codecov / codecov/patch

attachment_db_by_checksum/models/ir_attachment.py#L116

Added line #L116 was not covered by tests

# we don't know if previous storage was file system or DB:
# we run for every attachment
all_attachments = self._get_all_attachments_by_checksum()
for attach in all_attachments:
attach.write(
{
"datas": attach.datas,
# do not try to guess mimetype overwriting existing value
"mimetype": attach.mimetype,
}
)
return True

@api.model
def force_storage(self):
location = self._storage()
if location == HASHED_STORAGE_PARAMETER:
return self.force_storage_by_checksum()
return super().force_storage()

Check warning on line 136 in attachment_db_by_checksum/models/ir_attachment.py

View check run for this annotation

Codecov / codecov/patch

attachment_db_by_checksum/models/ir_attachment.py#L136

Added line #L136 was not covered by tests
43 changes: 43 additions & 0 deletions attachment_db_by_checksum/models/ir_attachment_content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from odoo import fields, models


class AttachmentContent(models.Model):
_name = "ir.attachment.content"
_rec_name = "checksum"
_description = "Attachment content by hash"

checksum = fields.Char(
string="Checksum/SHA1",
help="Checksum in the shape 2a/2a...\n",
index=True,
readonly=True,
required=True,
)
db_datas = fields.Binary(
string="Database Data",
attachment=False,
)

_sql_constraints = [
(
"checksum_uniq",
"unique(checksum)",
"The checksum of the file must be unique!",
),
]

def search_by_checksum(self, fname):
"""Get Attachment content, searching by `fname`.

Note that `fname` is the relative path of the attachment
as it would be saved by the core, for example 2a/2a...,
this is the same value that we store
in field `ir.attachment.content.checksum`.
"""
attachment_content = self.env["ir.attachment.content"].search(
[
("checksum", "=", fname),
],
limit=1,
)
return attachment_content
3 changes: 3 additions & 0 deletions attachment_db_by_checksum/readme/CONFIGURE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Set system parameter ``ir_attachment.location`` to ``hashed_db`` to activate saving by checksum.

Run ``force_storage``, method of ``ir.attachment``, to move existing attachments.
4 changes: 4 additions & 0 deletions attachment_db_by_checksum/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
* `TAKOBI <https://takobi.online>`_:

* Lorenzo Battistini
* Simone Rubino <sir@takobi.online>
3 changes: 3 additions & 0 deletions attachment_db_by_checksum/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Allow to identify database attachments through their hash, avoiding duplicates.

This is typically useful when you want to save attachments to database but you want to save space avoiding to write the same content in several attachments (think of email attachments, for example, or any file uploaded more than once).
4 changes: 4 additions & 0 deletions attachment_db_by_checksum/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_ir_attachment_all","Everyone can read Attachment Contents","model_ir_attachment_content",,1,0,0,0
"access_ir_attachment_group_user","Internal Users can manage Attachment Contents","model_ir_attachment_content","base.group_user",1,1,1,1
"access_ir_attachment_portal","Portal Users can read and create Attachment Contents","model_ir_attachment_content","base.group_portal",1,0,1,0
Loading
Loading