diff --git a/attachment_file_extension/README.rst b/attachment_file_extension/README.rst new file mode 100644 index 00000000..b877e307 --- /dev/null +++ b/attachment_file_extension/README.rst @@ -0,0 +1,34 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License: AGPL-3 + +Attachment File Extension +========================= + +This module was written to get attachment files readable by any external +software, expecially by those that need to recognise the file type from their +extension to open them properly. It allows to store attachments on filesystem +adding the file extension to their filename. + +Credits +======= + +Contributors +------------ + +* Alex Comba +* Lorenzo Battistini + +Maintainer +---------- + +.. image:: http://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: http://odoo-community.org + +This module is maintained by the OCA. + +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. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/attachment_file_extension/__init__.py b/attachment_file_extension/__init__.py new file mode 100644 index 00000000..4a741754 --- /dev/null +++ b/attachment_file_extension/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2015 Agile Business Group sagl () +# @author Alex Comba +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from . import models diff --git a/attachment_file_extension/__openerp__.py b/attachment_file_extension/__openerp__.py new file mode 100644 index 00000000..49c5499a --- /dev/null +++ b/attachment_file_extension/__openerp__.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2015 Agile Business Group sagl () +# @author Alex Comba +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +{ + "name": "Attachment File Extension", + "version": "1.0", + "category": "Knowledge Management", + "summary": """ +It allows to store attachments on filesystem adding the extension file to +their filename. +""", + 'author': "Agile Business Group, Odoo Community Association (OCA)", + "website": "http://www.agilebg.com", + "license": "AGPL-3", + "depends": [], + "data": [], + "installable": True, +} diff --git a/attachment_file_extension/models/__init__.py b/attachment_file_extension/models/__init__.py new file mode 100644 index 00000000..8891f3cf --- /dev/null +++ b/attachment_file_extension/models/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2015 Agile Business Group sagl () +# @author Alex Comba +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from . import ir_attachment diff --git a/attachment_file_extension/models/ir_attachment.py b/attachment_file_extension/models/ir_attachment.py new file mode 100644 index 00000000..22d3ed40 --- /dev/null +++ b/attachment_file_extension/models/ir_attachment.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2015 Agile Business Group sagl +# () +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.osv import orm, fields +import os.path +import threading + + +class IrAttachment(orm.Model): + _inherit = 'ir.attachment' + + def __init__(self, *args, **kwargs): + self._file_extension_lock = threading.RLock() + self._extension = None + super(IrAttachment, self).__init__(*args, **kwargs) + + def _full_path(self, cr, uid, path): + # every calls to this method is managed using by _file_extension_lock + # to avoid the risk of overwriting the _extension attribute + full_path = super(IrAttachment, self)._full_path(cr, uid, path) + if self._extension: + full_path += self._extension + return full_path + + def _data_set(self, cr, uid, id, name, value, arg, context=None): + with self._file_extension_lock: + attachment = self.browse(cr, uid, id, context=context) + if attachment.datas_fname: + filename, extension = os.path.splitext(attachment.datas_fname) + self._extension = extension + attachment.write({'extension': extension}) + super(IrAttachment, self)._data_set( + cr, uid, id, name, value, arg, context=context) + + def _data_get(self, cr, uid, ids, name, arg, context=None): + res = {} + for attachment in self.browse(cr, uid, ids, context=context): + if attachment.extension: + with self._file_extension_lock: + self._extension = attachment.extension + res.update( + super(IrAttachment, self)._data_get( + cr, uid, [attachment.id], name, arg, + context=context)) + else: + res.update( + super(IrAttachment, self)._data_get( + cr, uid, [attachment.id], name, arg, + context=context)) + return res + + def unlink(self, cr, uid, ids, context=None): + for attachment in self.browse(cr, uid, ids, context=context): + if attachment.extension: + with self._file_extension_lock: + self._extension = attachment.extension + super(IrAttachment, self).unlink( + cr, uid, ids, context=context) + else: + super(IrAttachment, self).unlink(cr, uid, ids, context=context) + + _columns = { + 'datas': fields.function( + _data_get, fnct_inv=_data_set, string='File Content', + type="binary", nodrop=True), + 'extension': fields.char(string='Attachment File Extension', size=4), + } diff --git a/attachment_file_extension/static/description/icon.png b/attachment_file_extension/static/description/icon.png new file mode 100644 index 00000000..e6a0f39f Binary files /dev/null and b/attachment_file_extension/static/description/icon.png differ diff --git a/attachment_file_extension/tests/test_attachment_file_extension.py b/attachment_file_extension/tests/test_attachment_file_extension.py new file mode 100644 index 00000000..465ffe4f --- /dev/null +++ b/attachment_file_extension/tests/test_attachment_file_extension.py @@ -0,0 +1,38 @@ +import hashlib +import os +from openerp.tests.common import TransactionCase + +HASH_SPLIT = 2 + + +class TestAttachmentFileEstension(TransactionCase): + + def setUp(self): + super(TestAttachmentFileEstension, self).setUp() + registry, cr, uid = self.registry, self.cr, self.uid + self.ira = registry('ir.attachment') + self.filestore = self.ira._filestore(cr, uid) + + # Blob + self.blob = 'blob' + self.blob_b64 = self.blob.encode('base64') + blob_hash = hashlib.sha1(self.blob).hexdigest() + self.blob_fname = blob_hash[:HASH_SPLIT] + '/' + blob_hash + self.blob_datas_fname = 'ira.ext' + + def test_01_store_on_disk(self): + cr, uid = self.cr, self.uid + + ira_id = self.ira.create( + cr, uid, {'name': 'ira', + 'datas': self.blob_b64, + 'datas_fname': self.blob_datas_fname}) + attachment = self.ira.browse(cr, uid, ira_id) + filename, extension = os.path.splitext(self.blob_datas_fname) + + self.assertEqual(attachment.store_fname, self.blob_fname) + self.assertEqual(attachment.extension, extension) + self.assertTrue( + os.path.isfile(os.path.join( + self.filestore, + attachment.store_fname + attachment.extension)))