[REF] base, mail: splits the documents feature from ir attachment

Moved the field 'active', 'res_model_name' and 'thumbnail' from
ir.attachment to enterprise's documents.document.

Moved the pdf split feature to enterprise's Documents module.

task: #1908896
This commit is contained in:
ThanhDodeurOdoo
2018-12-06 19:09:33 +00:00
parent 72905feee0
commit 89f20d2c29
8 changed files with 29 additions and 216 deletions
+2 -20
View File
@@ -13,7 +13,6 @@ var DocumentViewer = Widget.extend({
template: "DocumentViewer",
events: {
'click .o_download_btn': '_onDownload',
'click .o_split_btn': '_onSplitPDF',
'click .o_viewer_img': '_onImageClicked',
'click .o_viewer_video': '_onVideoClicked',
'click .move_next': '_onNext',
@@ -44,7 +43,7 @@ var DocumentViewer = Widget.extend({
init: function (parent, attachments, activeAttachmentID) {
this._super.apply(this, arguments);
this.attachment = _.filter(attachments, function (attachment) {
var match = attachment.type == 'url' ? attachment.url.match("(youtu|.png|.jpg|.gif)") : attachment.mimetype.match("(image|video|application/pdf|text)");
var match = attachment.type === 'url' ? attachment.url.match("(youtu|.png|.jpg|.gif)") : attachment.mimetype.match("(image|video|application/pdf|text)");
if (match) {
attachment.type = match[1];
@@ -67,6 +66,7 @@ var DocumentViewer = Widget.extend({
}
});
this.activeAttachment = _.findWhere(attachments, {id: activeAttachmentID});
this.modelName = 'ir.attachment';
this._reset();
},
/**
@@ -325,24 +325,6 @@ var DocumentViewer = Widget.extend({
this._zoom(scale);
}
},
/**
* @private
* @param {MouseEvent} e
*/
_onSplitPDF: function (e) {
e.stopPropagation();
var self = this;
var indices = $("input[name=Indices]").val();
var remainder = $("input[type=checkbox][name=remainder]").is(":checked");
this._rpc({
model: 'ir.attachment',
method: 'split_pdf',
args: [this.activeAttachment.id, indices, remainder],
}).always(function () {
self.trigger_up('document_viewer_attachment_changed');
self.destroy();
});
},
/**
* @private
* @param {MouseEvent} e
-21
View File
@@ -485,27 +485,6 @@ $o-mail-attachment-margin: 5px;
.o_image_caption {
bottom: 20%;
position: absolute;
.o_split_pdf_area {
background-color: #3a3a3a;
margin: 5px;
padding: 6px;
margin-left: 50px;
.o_split_btn {
vertical-align: baseline;
}
.o_page_number_input {
width: 13%;
color: black;
display: inline;
}
.o_remainder_input {
vertical-align: middle;
}
}
}
}
+7 -11
View File
@@ -35,6 +35,7 @@
-->
<t t-name="DocumentViewer.Content">
<div class="o_viewer_content">
<t t-set="model" t-value="widget.modelName"/>
<div class="o_viewer-header">
<span class="o_image_caption">
<i class="fa fa-picture-o mr8" t-if="widget.activeAttachment.type == 'image'" role="img" aria-label="Image" title="Image"/>
@@ -42,11 +43,6 @@
<i class="fa fa-video-camera mr8" t-if="widget.activeAttachment.type == 'video'" role="img" aria-label="Video" title="Video"/>
<t t-esc="widget.activeAttachment.name"/>
<a role="button" href="#" class="o_download_btn ml8 small" data-toggle="tooltip" data-placement="right" title="Download"><i class="fa fa-fw fa-download" role="img" aria-label="Download"/></a>
<span t-if="widget.activeAttachment.type == 'application/pdf'" class="o_split_pdf_area">
<label for="Indices">Extract pages:&amp;nbsp;</label><input class="o_page_number_input" name="Indices" placeholder="e.g. 1-5, 7, 8-9"/>
<label for="remainder">All pages:&amp;nbsp;</label><input class="o_remainder_input" type="checkbox" name="remainder" value="remainder"/>
<button class="btn btn-sm btn-primary o_split_btn" data-toggle="tooltip" title="Split">Split</button>
</span>
</span>
<a role="button" class="o_close_btn float-right" href="#" aria-label="Close" title="Close">×</a>
</div>
@@ -55,12 +51,12 @@
<div t-if="widget.activeAttachment.type == 'image'" class="o_loading_img text-center">
<i class="fa fa-circle-o-notch fa-spin text-gray-light fa-3x fa-fw" role="img" aria-label="Loading" title="Loading"/>
</div>
<img t-if="widget.activeAttachment.type == 'image'" class="o_viewer_img" t-attf-src="/web/image/#{widget.activeAttachment.id}?unique=1&amp;signature=#{widget.activeAttachment.checksum}" alt="Viewer"/>
<iframe class="mt32 o_viewer_pdf" t-if="widget.activeAttachment.type == 'application/pdf'" t-attf-src="/web/static/lib/pdfjs/web/viewer.html?file=/web/content/#{widget.activeAttachment.id}" />
<iframe class="mt32 o_viewer_text" t-if="(widget.activeAttachment.type || '').indexOf('text') !== -1" t-attf-src="/web/content/#{widget.activeAttachment.id}" />
<iframe class="mt32 o_viewer_text" t-if="widget.activeAttachment.type == 'youtu'" allow="autoplay; encrypted-media" width="560" height="315" t-attf-src="https://www.youtube.com/embed/#{widget.activeAttachment.youtube}"/>
<img t-if="widget.activeAttachment.type === 'image'" class="o_viewer_img" t-attf-src="/web/image/#{widget.activeAttachment.id}?unique=1&amp;signature=#{widget.activeAttachment.checksum}&amp;model=#{model}" alt="Viewer"/>
<iframe t-if="widget.activeAttachment.type == 'application/pdf'" class="mt32 o_viewer_pdf" t-attf-src="/web/static/lib/pdfjs/web/viewer.html?file=/web/content/#{widget.activeAttachment.id}?model%3D#{model}" />
<iframe t-if="(widget.activeAttachment.type || '').indexOf('text') !== -1" class="mt32 o_viewer_text" t-attf-src="/web/content/#{widget.activeAttachment.id}?model%3D#{model}" />
<iframe t-if="widget.activeAttachment.type == 'youtu'" class="mt32 o_viewer_text" allow="autoplay; encrypted-media" width="560" height="315" t-attf-src="https://www.youtube.com/embed/#{widget.activeAttachment.youtube}"/>
<video t-if="widget.activeAttachment.type == 'video'" class="o_viewer_video" controls="controls">
<source t-attf-src="/web/image/#{widget.activeAttachment.id}" t-att-data-type="widget.activeAttachment.mimetype"/>
<source t-attf-src="/web/image/#{widget.activeAttachment.id}?model=#{model}" t-att-data-type="widget.activeAttachment.mimetype"/>
</video>
</div>
</div>
@@ -86,7 +82,7 @@
-->
<t t-name="DocumentViewer">
<div class="modal o_modal_fullscreen" tabindex="-1" data-keyboard="false" role="dialog">
<t t-call="DocumentViewer.Content"/>
<t class="o_document_viewer_content_call" t-call="DocumentViewer.Content"/>
<t t-if="widget.attachment.length !== 1">
<a class="arrow arrow-left move_previous" href="#">
+2 -2
View File
@@ -1445,11 +1445,11 @@ QUnit.test('chatter: Attachment viewer', function (assert) {
"image caption should have correct download link");
// click on first image attachement
testUtils.dom.click(form.$('.o_thread_message .o_attachment .o_image_box .o_image_overlay').first());
assert.strictEqual($('.o_modal_fullscreen img.o_viewer_img[data-src="/web/image/1?unique=1&signature=999"]').length, 1,
assert.strictEqual($('.o_modal_fullscreen img.o_viewer_img[data-src="/web/image/1?unique=1&signature=999&model=ir.attachment"]').length, 1,
"Modal popup should open with first image src");
// click on next button
testUtils.dom.click($('.modal .arrow.arrow-right.move_next span'));
assert.strictEqual($('.o_modal_fullscreen img.o_viewer_img[data-src="/web/image/2?unique=1&signature=999"]').length, 1,
assert.strictEqual($('.o_modal_fullscreen img.o_viewer_img[data-src="/web/image/2?unique=1&signature=999&model=ir.attachment"]').length, 1,
"Modal popup should have now second image src");
assert.strictEqual($('.o_modal_fullscreen .o_viewer_toolbar .o_download_btn').length, 1,
"Modal popup should have download button");
@@ -19,16 +19,16 @@ var createViewer = function (params) {
var viewer = new DocumentViewer(parent, params.attachments, params.attachmentID);
var mockRPC = function (route) {
if (route === '/web/static/lib/pdfjs/web/viewer.html?file=/web/content/1') {
if (route === '/web/static/lib/pdfjs/web/viewer.html?file=/web/content/1?model%3Dir.attachment') {
return $.when();
}
if (route === 'https://www.youtube.com/embed/FYqW0Gdwbzk') {
return $.when();
}
if (route === '/web/content/4') {
if (route === '/web/content/4?model%3Dir.attachment') {
return $.when();
}
if (route === '/web/image/6?unique=1&signature=999') {
if (route === '/web/image/6?unique=1&signature=999&model=ir.attachment') {
return $.when();
}
};
@@ -100,38 +100,6 @@ QUnit.module('DocumentViewer', {
viewer.destroy();
});
QUnit.test('Document Viewer PDF', function (assert) {
assert.expect(6);
var viewer = createViewer({
attachmentID: 1,
attachments: this.attachments,
mockRPC: function (route, args) {
if (args.method === 'split_pdf') {
assert.deepEqual(args.args, [1, "", false], "should have the right arguments");
return $.when();
}
return this._super.apply(this, arguments);
},
intercepts: {
document_viewer_attachment_changed: function () {
assert.ok(true, "should trigger document_viewer_attachment_changed event");
}
},
});
assert.containsOnce(viewer, '.o_page_number_input',
"pdf should have a page input");
assert.containsOnce(viewer, '.o_remainder_input',
"pdf should have a remainder checkbox");
assert.containsOnce(viewer, '.o_split_btn',
"pdf should have a split button");
testUtils.dom.click(viewer.$('.o_split_btn'));
assert.ok(viewer.isDestroyed(), 'viewer should be destroyed');
});
QUnit.test('Document Viewer Youtube', function (assert) {
assert.expect(3);
@@ -165,7 +133,7 @@ QUnit.module('DocumentViewer', {
assert.strictEqual(viewer.$(".o_image_caption:contains('text.html')").length, 1,
"the viewer be on the right attachment");
assert.containsOnce(viewer, 'iframe[data-src="/web/content/4"]',
assert.containsOnce(viewer, 'iframe[data-src="/web/content/4?model%3Dir.attachment"]',
"there should be an iframe with the right src");
viewer.destroy();
@@ -197,7 +165,7 @@ QUnit.module('DocumentViewer', {
assert.strictEqual(viewer.$(".o_image_caption:contains('image.jpg')").length, 1,
"the viewer be on the right attachment");
assert.containsOnce(viewer, 'img[data-src="/web/image/6?unique=1&signature=999"]',
assert.containsOnce(viewer, 'img[data-src="/web/image/6?unique=1&signature=999&model=ir.attachment"]',
"there should be a video player");
viewer.destroy();
+2 -10
View File
@@ -144,9 +144,7 @@
<field name="url"/>
<field name="type"/>
<field name="create_date"/>
<field name="active"/>
<field name="name"/>
<field name="res_model_name"/>
<field name="res_name"/>
<templates>
<t t-name="kanban-box">
@@ -155,7 +153,7 @@
<div class="o_kanban_image_wrapper">
<t t-set="webimage" t-value="new RegExp('image.*(gif|jpeg|jpg|png)').test(record.mimetype.value)"/>
<div t-if="record.type.raw_value == 'url'" class="o_url_image fa fa-link fa-3x text-muted"/>
<img t-elif="webimage" t-attf-src="/web/image/#{record.id.raw_value}?field=thumbnail" width="100" height="100" alt="Document" class="o_attachment_image"/>
<img t-elif="webimage" t-attf-src="/web/image/#{record.id.raw_value}" width="100" height="100" alt="Document" class="o_attachment_image"/>
<div t-else="!webimage" class="o_image o_image_thumbnail" t-att-data-mimetype="record.mimetype.value"/>
</div>
</div>
@@ -170,13 +168,7 @@
</t>
</div>
<div class="o_kanban_record_body">
<t t-if="record.res_model_name.raw_value">
<strong><field name="res_model_name"/></strong>
<t t-if="record.res_name.raw_value">
<span t-att-title="record.res_name.raw_value">: <field name="res_name"/></span>
</t>
</t>
<t t-elif="record.type.raw_value == 'url'">
<t t-if="record.type.raw_value == 'url'">
<span class="o_document_url"><i class="fa fa-globe"/> <field name="url" widget="url"/></span>
</t>
<samp t-else="" class="text-muted"> </samp>
+1 -111
View File
@@ -6,17 +6,14 @@ import itertools
import logging
import mimetypes
import os
import io
import re
from collections import defaultdict
import uuid
from odoo import api, fields, models, tools, SUPERUSER_ID, exceptions, _
from odoo import api, fields, models, tools, SUPERUSER_ID, _
from odoo.exceptions import AccessError, ValidationError
from odoo.tools import config, human_size, ustr, html_escape
from odoo.tools.mimetypes import guess_mimetype
from odoo.tools import crop_image, image_resize_image
from PyPDF2 import PdfFileWriter, PdfFileReader
_logger = logging.getLogger(__name__)
@@ -46,14 +43,6 @@ class IrAttachment(models.Model):
record = self.env[attachment.res_model].browse(attachment.res_id)
attachment.res_name = record.display_name
@api.depends('res_model')
def _compute_res_model_name(self):
for record in self:
if record.res_model:
model = self.env['ir.model'].search([('model', '=', record.res_model)], limit=1)
if model:
record.res_model_name = model[0].name
@api.model
def _storage(self):
return self.env['ir.config_parameter'].sudo().get_param('ir_attachment.location', 'file')
@@ -290,7 +279,6 @@ class IrAttachment(models.Model):
description = fields.Text('Description')
res_name = fields.Char('Resource Name', compute='_compute_res_name', store=True)
res_model = fields.Char('Resource Model', readonly=True, help="The database object this attachment will be attached to.")
res_model_name = fields.Char(compute='_compute_res_model_name', store=True, index=True)
res_field = fields.Char('Resource Field', readonly=True)
res_id = fields.Integer('Resource ID', readonly=True, help="The record id this is attached to.")
company_id = fields.Many2one('res.company', string='Company', change_default=True,
@@ -312,8 +300,6 @@ class IrAttachment(models.Model):
checksum = fields.Char("Checksum/SHA1", size=40, index=True, readonly=True)
mimetype = fields.Char('Mime Type', readonly=True)
index_content = fields.Text('Indexed Content', readonly=True, prefetch=False)
active = fields.Boolean(default=True, string="Active", oldname='archived')
thumbnail = fields.Binary(readonly=1, attachment=True)
@api.model_cr_context
def _auto_init(self):
@@ -449,18 +435,6 @@ class IrAttachment(models.Model):
self.check('read')
return super(IrAttachment, self).read(fields, load=load)
def _make_thumbnail(self, vals):
if vals.get('datas') and not vals.get('res_field'):
vals['thumbnail'] = False
if vals.get('mimetype') and re.match('image.*(gif|jpeg|jpg|png)', vals['mimetype']):
try:
temp_image = crop_image(vals['datas'], type='center', size=(80, 80), ratio=(1, 1))
vals['thumbnail'] = image_resize_image(base64_source=temp_image, size=(80, 80),
encoding='base64')
except Exception:
pass
return vals
@api.multi
def write(self, vals):
self.check('write', values=vals)
@@ -469,8 +443,6 @@ class IrAttachment(models.Model):
vals.pop(field, False)
if 'mimetype' in vals or 'datas' in vals:
vals = self._check_contents(vals)
if all([not attachment.res_field for attachment in self]):
vals = self._make_thumbnail(vals)
return super(IrAttachment, self).write(vals)
@api.multi
@@ -502,7 +474,6 @@ class IrAttachment(models.Model):
for field in ('file_size', 'checksum'):
values.pop(field, False)
values = self._check_contents(values)
values = self._make_thumbnail(values)
self.browse().check('write', values=values)
return super(IrAttachment, self).create(vals_list)
@@ -518,87 +489,6 @@ class IrAttachment(models.Model):
def action_get(self):
return self.env['ir.actions.act_window'].for_xml_id('base', 'action_attachment')
def _make_pdf(self, output, name_ext):
"""
:param output: PdfFileWriter object.
:param name_ext: the additional name of the new attachment (page count).
:return: the id of the attachment.
"""
self.ensure_one()
try:
stream = io.BytesIO()
output.write(stream)
return self.copy({
'name': self.name+'-'+name_ext,
'datas_fname': os.path.splitext(self.datas_fname or self.name)[0]+'-'+name_ext+".pdf",
'datas': base64.b64encode(stream.getvalue()),
})
except Exception:
raise Exception
def _split_pdf_groups(self, pdf_groups=None, remainder=False):
"""
calls _make_pdf to create the a new attachment for each page section.
:param pdf_groups: a list of lists representing the pages to split: pages = [[1,1], [4,5], [7,7]]
:returns the list of the ID's of the new PDF attachments.
"""
self.ensure_one()
with io.BytesIO(base64.b64decode(self.datas)) as stream:
try:
input_pdf = PdfFileReader(stream)
except Exception:
raise exceptions.ValidationError(_("ERROR: Invalid PDF file!"))
max_page = input_pdf.getNumPages()
remainder_set = set(range(0, max_page))
new_pdf_ids = []
if not pdf_groups:
pdf_groups = []
for pages in pdf_groups:
pages[1] = min(max_page, pages[1])
pages[0] = min(max_page, pages[0])
if pages[0] == pages[1]:
name_ext = "%s" % (pages[0],)
else:
name_ext = "%s-%s" % (pages[0], pages[1])
output = PdfFileWriter()
for i in range(pages[0]-1, pages[1]):
output.addPage(input_pdf.getPage(i))
new_pdf_id = self._make_pdf(output, name_ext)
new_pdf_ids.append(new_pdf_id)
remainder_set = remainder_set.difference(set(range(pages[0] - 1, pages[1])))
if remainder:
for i in remainder_set:
output_page = PdfFileWriter()
name_ext = "%s" % (i + 1,)
output_page.addPage(input_pdf.getPage(i))
new_pdf_id = self._make_pdf(output_page, name_ext)
new_pdf_ids.append(new_pdf_id)
self.write({'active': False})
elif not len(remainder_set):
self.write({'active': False})
return new_pdf_ids
def split_pdf(self, indices=None, remainder=False):
"""
called by the Document Viewer's Split PDF button.
evaluates the input string and turns it into a list of lists to be processed by _split_pdf_groups
:param indices: the formatted string of pdf split (e.g. 1,5-10, 8-22, 29-34) o_page_number_input
:param remainder: bool, if true splits the non specified pages, one by one. form checkbox o_remainder_input
:returns the list of the ID's of the newly created pdf attachments.
"""
self.ensure_one()
if 'pdf' not in self.mimetype:
raise exceptions.ValidationError(_("ERROR: the file must be a PDF"))
if indices:
try:
pages = [[int(x) for x in x.split('-')] for x in indices.split(',')]
except ValueError:
raise exceptions.ValidationError(_("ERROR: Invalid list of pages to split. Example: 1,5-9,10"))
return self._split_pdf_groups(pdf_groups=[[min(x), max(x)] for x in pages], remainder=remainder)
return self._split_pdf_groups(remainder=remainder)
@api.model
def get_serve_attachment(self, url, extra_domain=None, extra_fields=None, order=None):
domain = [('type', '=', 'binary'), ('url', '=', url)] + (extra_domain or [])
+10 -4
View File
@@ -3,7 +3,6 @@
# ir_http modular http routing
#----------------------------------------------------------
import base64
import datetime
import hashlib
import logging
import mimetypes
@@ -23,7 +22,6 @@ from odoo.exceptions import AccessDenied, AccessError
from odoo.http import request, STATIC_CACHE, content_disposition
from odoo.tools import pycompat, consteq
from odoo.tools.mimetypes import guess_mimetype
from ast import literal_eval
from odoo.modules.module import get_resource_path, get_module_path
_logger = logging.getLogger(__name__)
@@ -260,6 +258,13 @@ class IrHttp(models.AbstractModel):
"""
return False
@classmethod
def _get_special_models(cls):
"""
:return: the set of models that have to pass through several specific fields checks:
used for the 'url' and the 'access_token' fields.
"""
return {'ir.attachment'}
@classmethod
def binary_content(cls, xmlid=None, model='ir.attachment', id=None, field='datas',
@@ -290,6 +295,7 @@ class IrHttp(models.AbstractModel):
:returns: (status, headers, content)
"""
env = env or request.env
attachment_models = cls._get_special_models()
# get object and content
obj = None
if xmlid:
@@ -301,7 +307,7 @@ class IrHttp(models.AbstractModel):
return (404, [], None)
# access token grant access
if model == 'ir.attachment' and access_token:
if model in attachment_models and access_token:
obj = obj.sudo()
if access_mode:
if not cls._check_access_mode(env, id, access_mode, model, access_token=access_token,
@@ -320,7 +326,7 @@ class IrHttp(models.AbstractModel):
# attachment by url check
module_resource_path = None
if model == 'ir.attachment' and obj.type == 'url' and obj.url:
if model in attachment_models and obj.type == 'url' and obj.url:
url_match = re.match("^/(\w+)/(.+)$", obj.url)
if url_match:
module = url_match.group(1)