diff options
| author | ThanhDodeurOdoo <tso@odoo.com> | 2018-12-06 10:51:40 +0000 |
|---|---|---|
| committer | ThanhDodeurOdoo <tso@odoo.com> | 2018-12-06 19:09:34 +0000 |
| commit | adab361fb53c2cadda08bb84d001ccdbe7608afa (patch) | |
| tree | 32c38e8181d0785c74ba634eb321f1dc8b8ee636 /addons/attachment_indexation | |
| parent | 20cbb6dc0ba20c165f2ff1e6f1bcd418729ed186 (diff) | |
[REF] document, attachment_indexation: renames the module document
This commit renames the module document into attachment_indexation
to provide a more descriptive name and avoid confusion with
enterprise's documents module.
task: #1908896
Diffstat (limited to 'addons/attachment_indexation')
7 files changed, 465 insertions, 0 deletions
diff --git a/addons/attachment_indexation/__init__.py b/addons/attachment_indexation/__init__.py new file mode 100644 index 00000000000..dc5e6b693d1 --- /dev/null +++ b/addons/attachment_indexation/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/addons/attachment_indexation/__manifest__.py b/addons/attachment_indexation/__manifest__.py new file mode 100644 index 00000000000..337e6557b72 --- /dev/null +++ b/addons/attachment_indexation/__manifest__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +{ + 'name': 'Attachments List and Document Indexation', + 'version': '2.1', + 'category': 'Document Management', + 'description': """ +Attachments list and document indexation +======================================== +* Show attachment on the top of the forms +* Document Indexation: odt +""", + 'depends': ['web'], + 'data': [ + 'views/document_templates.xml', + ], + 'installable': True, +} diff --git a/addons/attachment_indexation/models/__init__.py b/addons/attachment_indexation/models/__init__.py new file mode 100644 index 00000000000..3f80145982f --- /dev/null +++ b/addons/attachment_indexation/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import ir_attachment diff --git a/addons/attachment_indexation/models/ir_attachment.py b/addons/attachment_indexation/models/ir_attachment.py new file mode 100644 index 00000000000..2bc6aee234d --- /dev/null +++ b/addons/attachment_indexation/models/ir_attachment.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import io +import logging +import PyPDF2 +import xml.dom.minidom +import zipfile + +from odoo import api, models + +_logger = logging.getLogger(__name__) +FTYPES = ['docx', 'pptx', 'xlsx', 'opendoc', 'pdf'] + +def textToString(element): + buff = u"" + for node in element.childNodes: + if node.nodeType == xml.dom.Node.TEXT_NODE: + buff += node.nodeValue + elif node.nodeType == xml.dom.Node.ELEMENT_NODE: + buff += textToString(node) + return buff + + +class IrAttachment(models.Model): + _inherit = 'ir.attachment' + + def _index_docx(self, bin_data): + '''Index Microsoft .docx documents''' + buf = u"" + f = io.BytesIO(bin_data) + if zipfile.is_zipfile(f): + try: + zf = zipfile.ZipFile(f) + content = xml.dom.minidom.parseString(zf.read("word/document.xml")) + for val in ["w:p", "w:h", "text:list"]: + for element in content.getElementsByTagName(val): + buf += textToString(element) + "\n" + except Exception: + pass + return buf + + def _index_pptx(self, bin_data): + '''Index Microsoft .pptx documents''' + + buf = u"" + f = io.BytesIO(bin_data) + if zipfile.is_zipfile(f): + try: + zf = zipfile.ZipFile(f) + zf_filelist = [x for x in zf.namelist() if x.startswith('ppt/slides/slide')] + for i in range(1, len(zf_filelist) + 1): + content = xml.dom.minidom.parseString(zf.read('ppt/slides/slide%s.xml' % i)) + for val in ["a:t"]: + for element in content.getElementsByTagName(val): + buf += textToString(element) + "\n" + except Exception: + pass + return buf + + def _index_xlsx(self, bin_data): + '''Index Microsoft .xlsx documents''' + + buf = u"" + f = io.BytesIO(bin_data) + if zipfile.is_zipfile(f): + try: + zf = zipfile.ZipFile(f) + content = xml.dom.minidom.parseString(zf.read("xl/sharedStrings.xml")) + for val in ["t"]: + for element in content.getElementsByTagName(val): + buf += textToString(element) + "\n" + except Exception: + pass + return buf + + def _index_opendoc(self, bin_data): + '''Index OpenDocument documents (.odt, .ods...)''' + + buf = u"" + f = io.BytesIO(bin_data) + if zipfile.is_zipfile(f): + try: + zf = zipfile.ZipFile(f) + content = xml.dom.minidom.parseString(zf.read("content.xml")) + for val in ["text:p", "text:h", "text:list"]: + for element in content.getElementsByTagName(val): + buf += textToString(element) + "\n" + except Exception: + pass + return buf + + def _index_pdf(self, bin_data): + '''Index PDF documents''' + + buf = u"" + if bin_data.startswith(b'%PDF-'): + f = io.BytesIO(bin_data) + try: + pdf = PyPDF2.PdfFileReader(f, overwriteWarnings=False) + for page in pdf.pages: + buf += page.extractText() + except Exception: + pass + return buf + + @api.model + def _index(self, bin_data, datas_fname, mimetype): + for ftype in FTYPES: + buf = getattr(self, '_index_%s' % ftype)(bin_data) + if buf: + return buf + + return super(IrAttachment, self)._index(bin_data, datas_fname, mimetype) diff --git a/addons/attachment_indexation/static/src/js/attachment_indexation.js b/addons/attachment_indexation/static/src/js/attachment_indexation.js new file mode 100644 index 00000000000..b8437d70417 --- /dev/null +++ b/addons/attachment_indexation/static/src/js/attachment_indexation.js @@ -0,0 +1,200 @@ +odoo.define('document.document', function (require) { +"use strict"; + +var core = require('web.core'); +var Dialog = require('web.Dialog'); +var framework = require('web.framework'); +var Sidebar = require('web.Sidebar'); +var field_utils = require('web.field_utils'); + +var _t = core._t; + +Sidebar.include({ + /** + * @override + */ + init : function (parent, options) { + this._super.apply(this, arguments); + this.hasAttachments = options.viewType === "form"; + if (this.hasAttachments) { + this.sections.splice(1, 0, { 'name' : 'files', 'label' : _t('Attachment(s)'), }); + this.items.files = []; + this.fileuploadId = _.uniqueId('oe_fileupload'); + $(window).on(this.fileuploadId, this._onFileUploaded.bind(this)); + } + }, + /** + * Get the attachment linked to the record when the toolbar started + * + * @override + */ + start: function () { + var _super = this._super.bind(this); + var def = this.hasAttachments ? this._updateAttachments() : $.when(); + return def.then(_super); + }, + /** + * @override + */ + destroy: function () { + if (this.hasAttachments) { + $(window).off(this.fileuploadId); + } + this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + /** + * @override + */ + updateEnv: function (env) { + this.env = env; + var _super = _.bind(this._super, this, env); + var def = this.hasAttachments ? this._updateAttachments() : $.when(); + def.then(_super); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Process the attachments then rerender the toolbar + * + * @private + * @param {Object} attachments + */ + _processAttachments: function (attachments) { + //to display number in name if more then one attachment which has same name. + var self = this; + _.chain(attachments) + .groupBy(function (attachment) { return attachment.name; }) + .each(function (attachment) { + if (attachment.length > 1) { + _.map(attachment, function (attachment, i) { + attachment.name = _.str.sprintf(_t("%s (%s)"), attachment.name, i+1); + }); + } + }); + _.each(attachments,function (a) { + a.label = a.name; + if (a.type === "binary") { + a.url = '/web/content/' + a.id + '?download=true'; + } + a.create_date = field_utils.parse.datetime(a.create_date, 'create_date', {isUTC: true}); + a.create_date_string = field_utils.format.datetime(a.create_date, 'create_date', self.env.context.params); + a.write_date = field_utils.parse.datetime(a.write_date, 'write_date', {isUTC: true}); + a.write_date_string = field_utils.format.datetime(a.write_date, 'write_date', self.env.context.params); + }); + this.items.files = attachments; + }, + /** + * @private + * @override + */ + _redraw: function () { + this._super.apply(this, arguments); + if (this.hasAttachments) { + this.$('.o_sidebar_add_attachment .o_form_binary_form') + .change(this._onAddAttachment.bind(this)); + this.$('.o_sidebar_delete_attachment') + .click(this._onDeleteAttachment.bind(this)); + } + }, + /** + * Update the attachments to be displayed in the attachment section + * of the toolbar + * + * @private + */ + _updateAttachments: function () { + if (this.items.files === undefined) { + return $.when(); + } + var activeId = this.env.activeIds[0]; + if (!activeId) { + this.items.files = []; + return $.when(); + } else { + var domain = [ + ['res_model', '=', this.env.model], + ['res_id', '=', activeId], + ['type', 'in', ['binary', 'url']] + ]; + var fields = ['name', 'url', 'type', + 'create_uid', 'create_date', 'write_uid', 'write_date']; + return this._rpc({ + model: 'ir.attachment', + method: 'search_read', + context: this.env.context, + domain: domain, + fields: fields, + }).then(this._processAttachments.bind(this)); + } + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Method triggered when user click on 'add attachment' and select a file + * + * @private + * @param {Event} event + */ + _onAddAttachment: function (event) { + var $event = $(event.target); + if ($event.val() !== '') { + var $binaryForm = this.$('form.o_form_binary_form'); + $binaryForm.submit(); + $binaryForm.find('input[type=file]').prop('disabled', true); + $binaryForm.find('button').prop('disabled', true).find('img, span').toggle(); + this.$('.o_sidebar_add_attachment a').text(_t('Uploading...')); + framework.blockUI(); + } + }, + /** + * Method triggered when user delete an attachment + * + * @private + * @param {Event} event + */ + _onDeleteAttachment: function (event) { + event.preventDefault(); + var self = this; + var $event = $(event.currentTarget); + var options = { + confirm_callback: function () { + self._rpc({ + model: 'ir.attachment', + method: 'unlink', + args: [parseInt($event.attr('data-id'), 10)], + }) + .then(self._updateAttachments.bind(self)) + .then(self._redraw.bind(self)); + } + }; + Dialog.confirm(this, _t("Do you really want to delete this attachment ?"), options); + }, + /** + * Handler called when the upload is done + * + * @private + */ + _onFileUploaded: function () { + var attachments = Array.prototype.slice.call(arguments, 1); + var uploadErrors = _.filter(attachments, function (attachment) { + return attachment.error; + }); + if (uploadErrors.length) { + this.do_warn(_t('Uploading Error'), uploadErrors[0].error); + } + this._updateAttachments().then(this._redraw.bind(this)); + framework.unblockUI(); + } +}); + +}); diff --git a/addons/attachment_indexation/static/tests/attachment_indexation_tests.js b/addons/attachment_indexation/static/tests/attachment_indexation_tests.js new file mode 100644 index 00000000000..33c081a28ac --- /dev/null +++ b/addons/attachment_indexation/static/tests/attachment_indexation_tests.js @@ -0,0 +1,112 @@ +odoo.define('document.tests', function (require) { + "use strict"; + + var testUtils = require('web.test_utils'); + var FormView = require('web.FormView'); + var ListView = require('web.ListView'); + + var createView = testUtils.createView; + + QUnit.module('DocumentTest', { + beforeEach: function () { + this.data = { + partner: { + fields: { + display_name: { string: "Displayed name", type: "char" } + }, + records: [{ + id: 1, + display_name: "first record", + }, { + id: 2, + display_name: "second record", + }] + }, + 'ir.attachment': { + fields: { + type: { string: "Type", type: "char" }, + name: {string: "Name", type: "char"}, + res_id: {string: "ResId", type: "integer"}, + res_model: {string: "ResModel", type: "char"} + }, + records: [{ + id: 1, + type:"binary", + name: "attachment1", + res_id: 1, + res_model: 'partner' + },{ + id: 2, + type:"binary", + name: "attachment2", + res_id: 1, + res_model: 'partner' + }] + } + }; + } + }, function () { + QUnit.module('DocView'); + QUnit.test('documentAttachmentTest', function (assert) { + assert.expect(3); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<sheet>' + + '<field name="display_name"/>' + + '</sheet>' + + '</form>', + res_id: 1, + viewOptions: {hasSidebar: true}, + mockRPC: function (route, args) { + if (args.method === 'search_read' && args.model === 'ir.attachment') { + return $.when(this.data['ir.attachment'].records); + } + if (route === '/web/dataset/call_kw/ir.attachment/unlink') { + assert.strictEqual(args.args[0], 1, "Should have correct id of the attachment to be deleted"); + } + return this._super.apply(this, arguments); + } + }); + + assert.containsN(form.sidebar, '.o_sidebar_delete_attachment', 2, "there should be two attachments"); + testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains("Attachment")')); + testUtils.dom.click(form.sidebar.$('.o_sidebar_delete_attachment:eq(0)')); + testUtils.dom.click($('.modal-footer .btn-primary')); + assert.containsOnce(form.sidebar, '.o_sidebar_delete_attachment', "there should be only one attachment"); + form.destroy(); + }); + + QUnit.test('no attachment on list view', function (assert) { + assert.expect(4); + + var list = createView({ + View: ListView, + model: 'partner', + data: this.data, + groupBy: ['display_name'], + viewOptions: {sidebar: true}, + arch: '<tree string="Partners">' + + '<field name="display_name"/>' + + '</tree>', + mockRPC: function (route, args) { + assert.step(args.model); + return this._super.apply(this, arguments); + } + }); + + // select record then trigger render + testUtils.dom.click(list.$('.o_group_header:last')); + testUtils.dom.click(list.$('.o_data_row input')); + testUtils.dom.click(list.$('.o_group_header:first')); + + assert.verifySteps(['partner', 'partner', 'partner'], + "ir.attachment not called when selecting record in list view"); + + list.destroy(); + }); + }); +}); diff --git a/addons/attachment_indexation/views/attachment_indexation_templates.xml b/addons/attachment_indexation/views/attachment_indexation_templates.xml new file mode 100644 index 00000000000..1b7b999fdd1 --- /dev/null +++ b/addons/attachment_indexation/views/attachment_indexation_templates.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <template id="assets_backend" name="document assets" inherit_id="web.assets_backend"> + <xpath expr="." position="inside"> + <script type="text/javascript" src="/document/static/src/js/document.js"></script> + </xpath> + </template> + + <template id="qunit_suite" name="document tests" inherit_id="web.qunit_suite"> + <xpath expr="//script[last()]" position="after"> + <script type="text/javascript" src="/document/static/tests/document_tests.js"/> + </xpath> + </template> +</odoo> |
