summaryrefslogtreecommitdiff
path: root/addons/attachment_indexation
diff options
context:
space:
mode:
authorThanhDodeurOdoo <tso@odoo.com>2018-12-06 10:51:40 +0000
committerThanhDodeurOdoo <tso@odoo.com>2018-12-06 19:09:34 +0000
commitadab361fb53c2cadda08bb84d001ccdbe7608afa (patch)
tree32c38e8181d0785c74ba634eb321f1dc8b8ee636 /addons/attachment_indexation
parent20cbb6dc0ba20c165f2ff1e6f1bcd418729ed186 (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')
-rw-r--r--addons/attachment_indexation/__init__.py4
-rw-r--r--addons/attachment_indexation/__manifest__.py18
-rw-r--r--addons/attachment_indexation/models/__init__.py4
-rw-r--r--addons/attachment_indexation/models/ir_attachment.py113
-rw-r--r--addons/attachment_indexation/static/src/js/attachment_indexation.js200
-rw-r--r--addons/attachment_indexation/static/tests/attachment_indexation_tests.js112
-rw-r--r--addons/attachment_indexation/views/attachment_indexation_templates.xml14
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>