diff --git a/.travis.yml b/.travis.yml index afb30a25..276e543d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ addons: apt: packages: - expect-dev # provides unbuffer utility + - python-magic language: python diff --git a/attachment_preview/README.rst b/attachment_preview/README.rst index 4f6feebe..b0781e3d 100644 --- a/attachment_preview/README.rst +++ b/attachment_preview/README.rst @@ -1,15 +1,48 @@ +=================== Preview attachments =================== +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fknowledge-lightgray.png?logo=github + :target: https://github.com/OCA/knowledge/tree/10.0/attachment_preview + :alt: OCA/knowledge +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/knowledge-10-0/knowledge-10-0-attachment_preview + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/118/10.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + This addon allows to preview attachments supported by http://viewerjs.org. Currently, that's most Libreoffice files and PDFs. +.. image:: /attachment_preview/static/description/screenshot-split.png + :alt: Screenshot of split form view + :width: 100% + +**Table of contents** + +.. contents:: + :local: + Installation ============ For filetype recognition, you'll get the best results by installing -``python-magic``:: +``python-magic``: sudo apt-get install python-magic @@ -17,30 +50,60 @@ Usage ===== The module adds a little print preview icon right of download links for -attachments or binary fields. +attachments or binary fields. When a preview is opened from the attachments +menu it's shown next to the form view. From this screen you can navigate +through the attachments using the arrow buttons. Using the pop-out button +next to the navigational buttons you can open the preview in a separate window. + +.. image:: /attachment_preview/static/description/screenshot-paginator.png + :alt: Screenshot navigator + +Bug Tracker +=========== + +Bugs are tracked on `GitHub 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 `_. + +Do not contact contributors directly about support or help with technical issues. Credits ======= +Authors +~~~~~~~ + +* Therp BV +* Onestein + +Contributors +~~~~~~~~~~~~ + +* Holger Brunn +* Dennis Sluijk + +Other credits +~~~~~~~~~~~~~ + Addon icon ---------- * courtesy of http://commons.wikimedia.org/wiki/Crystal_Clear -Contributors ------------- - -* Holger Brunn - -Maintainer ----------- - -.. image:: http://odoo-community.org/logo.png - :alt: Odoo Community Association - :target: http://odoo-community.org +Maintainers +~~~~~~~~~~~ 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. +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org -To contribute to this module, please visit http://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. + +This module is part of the `OCA/knowledge `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/attachment_preview/__init__.py b/attachment_preview/__init__.py index fcf92314..5b89c089 100644 --- a/attachment_preview/__init__.py +++ b/attachment_preview/__init__.py @@ -1,21 +1,4 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2014 Therp BV (). -# -# 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 model +# Copyright 2014 Therp BV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/attachment_preview/__manifest__.py b/attachment_preview/__manifest__.py index 01012f0d..bb3c9c49 100644 --- a/attachment_preview/__manifest__.py +++ b/attachment_preview/__manifest__.py @@ -1,46 +1,23 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2014 Therp BV (). -# -# 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 . -# -############################################################################## +# Copyright 2014 Therp BV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + { "name": "Preview attachments", - "version": "8.0.1.1.0", - "author": "Therp BV,Odoo Community Association (OCA)", + "version": "10.0.1.3.0", + "author": "Therp BV," + "Onestein," + "Odoo Community Association (OCA)", "license": "AGPL-3", - "complexity": "normal", - 'summary': 'Preview attachments supported by Viewer.js', + "summary": 'Preview attachments supported by Viewer.js', "category": "Knowledge Management", "depends": [ 'web', ], "data": [ - "view/attachment_preview.xml", + "templates/assets.xml", ], "qweb": [ 'static/src/xml/attachment_preview.xml', ], - "test": [ - ], - "auto_install": False, - 'installable': False, - "application": False, - "external_dependencies": { - 'python': [], - }, + "installable": True, } diff --git a/attachment_preview/model/__init__.py b/attachment_preview/model/__init__.py deleted file mode 100644 index 4466fe6d..00000000 --- a/attachment_preview/model/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2014 Therp BV (). -# -# 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_preview/model/ir_attachment.py b/attachment_preview/model/ir_attachment.py deleted file mode 100644 index b00d3474..00000000 --- a/attachment_preview/model/ir_attachment.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# This module copyright (C) 2014 Therp BV (). -# -# 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 . -# -############################################################################## -import collections -import os.path -import mimetypes -import base64 -from openerp.osv.orm import Model - - -class IrAttachment(Model): - _inherit = 'ir.attachment' - - def get_binary_extension( - self, cr, uid, model, ids, binary_field, filename_field=None, - context=None): - result = {} - for this in self.pool[model].browse( - cr, uid, - ids if isinstance(ids, collections.Iterable) else [ids], - context=context): - if not this.id: - result[this.id] = False - continue - extension = '' - if filename_field and this[filename_field]: - filename, extension = os.path.splitext(this[filename_field]) - if not this[binary_field]: - result[this.id] = False - continue - if not extension: - try: - import magic - ms = magic.open( - hasattr(magic, 'MAGIC_MIME_TYPE') and - magic.MAGIC_MIME_TYPE or magic.MAGIC_MIME) - ms.load() - mimetype = ms.buffer( - base64.b64decode(this[binary_field])) - except ImportError: - (mimetype, encoding) = mimetypes.guess_type( - 'data:;base64,' + this[binary_field], strict=False) - extension = mimetypes.guess_extension( - mimetype.split(';')[0], strict=False) - - result[this.id] = (extension or '').lstrip('.').lower() - return result if isinstance(ids, collections.Iterable) else result[ids] - - def get_attachment_extension(self, cr, uid, ids, context=None): - return self.get_binary_extension( - cr, uid, self._name, ids, 'datas', 'datas_fname', context=context) diff --git a/attachment_preview/models/__init__.py b/attachment_preview/models/__init__.py new file mode 100644 index 00000000..22aa81fb --- /dev/null +++ b/attachment_preview/models/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2014 Therp BV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import ir_attachment diff --git a/attachment_preview/models/ir_attachment.py b/attachment_preview/models/ir_attachment.py new file mode 100644 index 00000000..10cfd74a --- /dev/null +++ b/attachment_preview/models/ir_attachment.py @@ -0,0 +1,77 @@ +# Copyright 2014 Therp BV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import collections +import logging +import mimetypes +import os.path +import base64 + +from odoo import models, api + +_logger = logging.getLogger(__name__) + + +class IrAttachment(models.Model): + _inherit = 'ir.attachment' + + @api.model + def get_binary_extension(self, model, ids, binary_field, + filename_field=None): + result = {} + ids_to_browse = ids if isinstance(ids, collections.Iterable) else [ids] + + # First pass: load fields in bin_size mode to avoid loading big files + # unnecessarily. + if filename_field: + for this in self.env[model].with_context( + bin_size=True).browse(ids_to_browse): + extension = '' + if this[filename_field]: + filename, extension = os.path.splitext( + this[filename_field]) + if this[binary_field] and extension: + result[this.id] = extension + _logger.debug('Got extension %s from filename %s', + extension, this[filename_field]) + # Second pass for all attachments which have to be loaded fully + # to get the extension from the content + ids_to_browse = [_id for _id in ids_to_browse if _id not in result] + for this in self.env[model].with_context( + bin_size=True).browse(ids_to_browse): + if not this[binary_field]: + result[this.id] = False + continue + try: + import magic + ms = magic.open( + hasattr(magic, 'MAGIC_MIME_TYPE') and + magic.MAGIC_MIME_TYPE or magic.MAGIC_MIME) + ms.load() + if model == self._name and binary_field == 'datas'\ + and this.store_fname: + mimetype = ms.file( + this._full_path(this.store_fname)) + _logger.debug('Magic determined mimetype %s from file %s', + mimetype, this.store_fname) + else: + mimetype = ms.buffer( + base64.b64decode(this[binary_field])) + _logger.debug('Magic determined mimetype %s from buffer', + mimetype) + except ImportError: + (mimetype, encoding) = mimetypes.guess_type( + 'data:;base64,' + this[binary_field], strict=False) + _logger.debug('Mimetypes guessed type %s from buffer', + mimetype) + extension = mimetypes.guess_extension( + mimetype.split(';')[0], strict=False) + result[this.id] = extension + for _id in result: + result[_id] = (result[_id] or '').lstrip('.').lower() + return result if isinstance(ids, collections.Iterable) else result[ids] + + @api.model + def get_attachment_extension(self, ids): + return self.get_binary_extension( + self._name, ids, 'datas', 'datas_fname') diff --git a/attachment_preview/readme/CONTRIBUTORS.rst b/attachment_preview/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..81c9fb26 --- /dev/null +++ b/attachment_preview/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Holger Brunn +* Dennis Sluijk diff --git a/attachment_preview/readme/CREDITS.rst b/attachment_preview/readme/CREDITS.rst new file mode 100644 index 00000000..07bb3a4e --- /dev/null +++ b/attachment_preview/readme/CREDITS.rst @@ -0,0 +1,4 @@ +Addon icon +---------- + +* courtesy of http://commons.wikimedia.org/wiki/Crystal_Clear diff --git a/attachment_preview/readme/DESCRIPTION.rst b/attachment_preview/readme/DESCRIPTION.rst new file mode 100644 index 00000000..aadfd944 --- /dev/null +++ b/attachment_preview/readme/DESCRIPTION.rst @@ -0,0 +1,7 @@ +This addon allows to preview attachments supported by http://viewerjs.org. + +Currently, that's most Libreoffice files and PDFs. + +.. image:: /attachment_preview/static/description/screenshot-split.png + :alt: Screenshot of split form view + :width: 100% diff --git a/attachment_preview/readme/INSTALL.rst b/attachment_preview/readme/INSTALL.rst new file mode 100644 index 00000000..eb26fced --- /dev/null +++ b/attachment_preview/readme/INSTALL.rst @@ -0,0 +1,4 @@ +For filetype recognition, you'll get the best results by installing +``python-magic``: + +sudo apt-get install python-magic diff --git a/attachment_preview/readme/USAGE.rst b/attachment_preview/readme/USAGE.rst new file mode 100644 index 00000000..941a680c --- /dev/null +++ b/attachment_preview/readme/USAGE.rst @@ -0,0 +1,8 @@ +The module adds a little print preview icon right of download links for +attachments or binary fields. When a preview is opened from the attachments +menu it's shown next to the form view. From this screen you can navigate +through the attachments using the arrow buttons. Using the pop-out button +next to the navigational buttons you can open the preview in a separate window. + +.. image:: /attachment_preview/static/description/screenshot-paginator.png + :alt: Screenshot navigator diff --git a/attachment_preview/static/description/screenshot-paginator.png b/attachment_preview/static/description/screenshot-paginator.png new file mode 100644 index 00000000..02910c83 Binary files /dev/null and b/attachment_preview/static/description/screenshot-paginator.png differ diff --git a/attachment_preview/static/description/screenshot-split.png b/attachment_preview/static/description/screenshot-split.png new file mode 100644 index 00000000..257952bf Binary files /dev/null and b/attachment_preview/static/description/screenshot-split.png differ diff --git a/attachment_preview/static/lib/ViewerJS/compatibility.js b/attachment_preview/static/lib/ViewerJS/compatibility.js index 967e312a..06f54bff 100644 --- a/attachment_preview/static/lib/ViewerJS/compatibility.js +++ b/attachment_preview/static/lib/ViewerJS/compatibility.js @@ -447,20 +447,10 @@ if (typeof PDFJS === 'undefined') { // Checks if navigator.language is supported (function checkNavigatorLanguage() { - if ('language' in navigator && - /^[a-z]+(-[A-Z]+)?$/.test(navigator.language)) { + if ('language' in navigator) { return; } - function formatLocale(locale) { - var split = locale.split(/[-_]/); - split[0] = split[0].toLowerCase(); - if (split.length > 1) { - split[1] = split[1].toUpperCase(); - } - return split.join('-'); - } - var language = navigator.language || navigator.userLanguage || 'en-US'; - PDFJS.locale = formatLocale(language); + PDFJS.locale = navigator.userLanguage || 'en-US'; })(); (function checkRangeRequests() { @@ -479,7 +469,10 @@ if (typeof PDFJS === 'undefined') { var regex = /Android\s[0-2][^\d]/; var isOldAndroid = regex.test(navigator.userAgent); - if (isSafari || isOldAndroid) { + // Range requests are broken in Chrome 39 and 40, https://crbug.com/442318 + var isChromeWithRangeBug = /Chrome\/(39|40)\./.test(navigator.userAgent); + + if (isSafari || isOldAndroid || isChromeWithRangeBug) { PDFJS.disableRange = true; PDFJS.disableStream = true; } @@ -572,3 +565,13 @@ if (typeof PDFJS === 'undefined') { PDFJS.maxCanvasPixels = 5242880; } })(); + +// Disable fullscreen support for certain problematic configurations. +// Support: IE11+ (when embedded). +(function checkFullscreenSupport() { + var isEmbeddedIE = (navigator.userAgent.indexOf('Trident') >= 0 && + window.parent !== window); + if (isEmbeddedIE) { + PDFJS.disableFullscreen = true; + } +})(); diff --git a/attachment_preview/static/lib/ViewerJS/index.html b/attachment_preview/static/lib/ViewerJS/index.html index c7496ceb..c79df466 100644 --- a/attachment_preview/static/lib/ViewerJS/index.html +++ b/attachment_preview/static/lib/ViewerJS/index.html @@ -39,37 +39,37 @@ along with ViewerJS. If not, see . + + + + diff --git a/attachment_preview/tests/__init__.py b/attachment_preview/tests/__init__.py new file mode 100644 index 00000000..7aec7faa --- /dev/null +++ b/attachment_preview/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2018 Onestein +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_attachment_preview diff --git a/attachment_preview/tests/test_attachment_preview.py b/attachment_preview/tests/test_attachment_preview.py new file mode 100644 index 00000000..1a3ab7f0 --- /dev/null +++ b/attachment_preview/tests/test_attachment_preview.py @@ -0,0 +1,57 @@ +# Copyright 2018 Onestein +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import base64 +from odoo.tests.common import TransactionCase + + +class TestAttachmentPreview(TransactionCase): + def test_get_extension(self): + attachment = self.env['ir.attachment'].create({ + 'datas': base64.b64encode(b'from this, to that.'), + 'name': 'doc.txt', + 'datas_fname': 'doc.txt' + }) + attachment2 = self.env['ir.attachment'].create({ + 'datas': base64.b64encode(b'Png'), + 'name': 'image.png', + 'datas_fname': 'image.png' + }) + res = self.env['ir.attachment'].get_attachment_extension( + attachment.id + ) + self.assertEqual(res, 'txt') + + res = self.env['ir.attachment'].get_attachment_extension( + [attachment.id, attachment2.id] + ) + self.assertEqual(res[attachment.id], 'txt') + self.assertEqual(res[attachment2.id], 'png') + + res2 = self.env['ir.attachment'].get_binary_extension( + 'ir.attachment', + attachment.id, + 'datas' + ) + self.assertTrue(res2) + + modules = self.env['ir.module.module'].search([]) + module = modules.filtered( + lambda m: m.icon_image + )[0] + res3 = self.env['ir.attachment'].get_binary_extension( + 'ir.module.module', + module.id, + 'icon_image' + ) + self.assertTrue(res3) + + att1 = self.env['ir.attachment'].create({ + 'name': 'Test Attachment Preview' + }) + res4 = self.env['ir.attachment'].get_binary_extension( + 'ir.attachment', + att1.id, + 'datas' + ) + self.assertFalse(res4) diff --git a/attachment_preview/view/attachment_preview.xml b/attachment_preview/view/attachment_preview.xml deleted file mode 100644 index a2d6dcf1..00000000 --- a/attachment_preview/view/attachment_preview.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - -