mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2025-02-25 18:55:21 -06:00
[PATCH] Upstream patch - 18092022
This commit is contained in:
@@ -6,6 +6,7 @@ import xml.dom.minidom
|
||||
import zipfile
|
||||
|
||||
from flectra import api, models
|
||||
from flectra.tools.lru import LRU
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -21,6 +22,8 @@ except ImportError:
|
||||
FTYPES = ['docx', 'pptx', 'xlsx', 'opendoc', 'pdf']
|
||||
|
||||
|
||||
index_content_cache = LRU(1)
|
||||
|
||||
def textToString(element):
|
||||
buff = u""
|
||||
for node in element.childNodes:
|
||||
@@ -121,10 +124,19 @@ class IrAttachment(models.Model):
|
||||
return buf
|
||||
|
||||
@api.model
|
||||
def _index(self, bin_data, mimetype):
|
||||
def _index(self, bin_data, mimetype, checksum=None):
|
||||
if checksum:
|
||||
cached_content = index_content_cache.get(checksum)
|
||||
if cached_content:
|
||||
return cached_content
|
||||
res = False
|
||||
for ftype in FTYPES:
|
||||
buf = getattr(self, '_index_%s' % ftype)(bin_data)
|
||||
if buf:
|
||||
return buf.replace('\x00', '')
|
||||
res = buf.replace('\x00', '')
|
||||
break
|
||||
|
||||
return super(IrAttachment, self)._index(bin_data, mimetype)
|
||||
res = res or super(IrAttachment, self)._index(bin_data, mimetype, checksum=checksum)
|
||||
if checksum:
|
||||
index_content_cache[checksum] = res
|
||||
return res
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
from . import controllers
|
||||
from . import models
|
||||
|
||||
1
addons/l10n_ar_website_sale/models/__init__.py
Normal file
1
addons/l10n_ar_website_sale/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import ir_ui_view
|
||||
14
addons/l10n_ar_website_sale/models/ir_ui_view.py
Normal file
14
addons/l10n_ar_website_sale/models/ir_ui_view.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from flectra import _, api, models
|
||||
from flectra.exceptions import ValidationError
|
||||
|
||||
|
||||
class View(models.Model):
|
||||
_inherit = 'ir.ui.view'
|
||||
|
||||
@api.constrains('active', 'key', 'website_id')
|
||||
def _check_active(self):
|
||||
for record in self:
|
||||
if record.key == 'website_sale.address_b2b' and record.website_id:
|
||||
if record.website_id.company_id.country_id.code == "AR" and not record.active:
|
||||
raise ValidationError(_("B2B fields must always be displayed with Argentinian website."))
|
||||
@@ -12,20 +12,20 @@ tour.register('sale_matrix_tour', {
|
||||
trigger: ".o_list_button_add",
|
||||
extra_trigger: ".o_sale_order"
|
||||
}, {
|
||||
trigger: "a:contains('Add a product')"
|
||||
trigger: '.o_required_modifier[name=partner_id] input',
|
||||
run: 'text Agrolait',
|
||||
}, {
|
||||
trigger: '.ui-menu-item > a:contains("Agrolait")',
|
||||
auto: true,
|
||||
}, {
|
||||
trigger: 'a:contains("Add a product")',
|
||||
}, {
|
||||
trigger: '.o_data_cell',
|
||||
}, {
|
||||
trigger: 'div[name="product_template_id"] input',
|
||||
run: function () {
|
||||
var $input = $('div[name="product_template_id"] input');
|
||||
$input.click();
|
||||
$input.val('Matrix');
|
||||
var keyDownEvent = jQuery.Event("keydown");
|
||||
keyDownEvent.which = 42;
|
||||
$input.trigger(keyDownEvent);
|
||||
}
|
||||
run: 'text Matrix',
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete a:contains("Matrix")',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.o_product_variant_matrix',
|
||||
run: function () {
|
||||
@@ -34,14 +34,15 @@ tour.register('sale_matrix_tour', {
|
||||
}
|
||||
}, {
|
||||
trigger: 'span:contains("Confirm")',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.o_sale_order',
|
||||
// wait for qty to be 1 => check the total to be sure all qties are set to 1
|
||||
extra_trigger: '.oe_subtotal_footer_separator:contains("18.40")',
|
||||
}, {
|
||||
trigger: 'span:contains("Matrix (PAV11, PAV22, PAV31)\n\nPA4: PAV41")',
|
||||
extra_trigger: '.o_form_editable',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.o_edit_product_configuration',
|
||||
run: 'click' // edit the matrix
|
||||
trigger: '.o_edit_product_configuration', // edit the matrix
|
||||
}, {
|
||||
trigger: '.o_product_variant_matrix',
|
||||
run: function () {
|
||||
@@ -49,15 +50,16 @@ tour.register('sale_matrix_tour', {
|
||||
$('.o_matrix_input').val(3);
|
||||
}
|
||||
}, {
|
||||
trigger: 'span:contains("Confirm")',
|
||||
run: 'click' // apply the matrix
|
||||
trigger: 'span:contains("Confirm")', // apply the matrix
|
||||
}, {
|
||||
trigger: '.o_sale_order',
|
||||
// wait for qty to be changed => check the total to be sure all qties are set to 3
|
||||
extra_trigger: '.oe_subtotal_footer_separator:contains("55.20")',
|
||||
}, {
|
||||
trigger: 'span:contains("Matrix (PAV11, PAV22, PAV31)\n\nPA4: PAV41")',
|
||||
extra_trigger: '.o_form_editable',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.o_edit_product_configuration',
|
||||
run: 'click' // edit the matrix
|
||||
}, {
|
||||
trigger: '.o_product_variant_matrix',
|
||||
run: function () {
|
||||
@@ -66,30 +68,21 @@ tour.register('sale_matrix_tour', {
|
||||
}
|
||||
}, {
|
||||
trigger: 'span:contains("Confirm")',
|
||||
run: 'click' // apply the matrix
|
||||
}, {
|
||||
trigger: ".o_form_editable .o_field_many2one[name='partner_id'] input",
|
||||
extra_trigger: ".o_sale_order",
|
||||
run: 'text Agrolait'
|
||||
trigger: '.o_sale_order',
|
||||
// wait for qty to be 1 => check the total to be sure all qties are reset to 1
|
||||
extra_trigger: '.oe_subtotal_footer_separator:contains("18.40")',
|
||||
}, {
|
||||
trigger: ".ui-menu-item > a",
|
||||
auto: true,
|
||||
in_modal: false,
|
||||
}, {
|
||||
trigger: '.o_form_button_save:contains("Save")',
|
||||
run: 'click' // SAVE Sales Order.
|
||||
trigger: '.o_form_button_save:contains("Save")', // SAVE Sales Order.
|
||||
},
|
||||
// Open the matrix through the pencil button next to the product in line edit mode.
|
||||
{
|
||||
trigger: '.o_form_button_edit:contains("Edit")',
|
||||
run: 'click' // Edit Sales Order.
|
||||
trigger: '.o_form_button_edit:contains("Edit")', // Edit Sales Order.
|
||||
}, {
|
||||
trigger: 'span:contains("Matrix (PAV11, PAV22, PAV31)\n\nPA4: PAV41")',
|
||||
extra_trigger: '.o_form_editable',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: '.o_edit_product_configuration',
|
||||
run: 'click' // edit the matrix
|
||||
trigger: '.o_edit_product_configuration', // edit the matrix
|
||||
}, {
|
||||
trigger: '.o_product_variant_matrix',
|
||||
run: function () {
|
||||
@@ -97,32 +90,24 @@ tour.register('sale_matrix_tour', {
|
||||
$('.o_matrix_input').slice(8, 16).val(4);
|
||||
} // set the qty to 4 for half of the matrix products.
|
||||
}, {
|
||||
trigger: 'span:contains("Confirm")',
|
||||
run: 'click' // apply the matrix
|
||||
trigger: 'span:contains("Confirm")', // apply the matrix
|
||||
}, {
|
||||
trigger: '.o_sale_order',
|
||||
// wait for qty to be changed => check the total to be sure all qties are set to either 1 or 4
|
||||
extra_trigger: '.oe_subtotal_footer_separator:contains("46.00")',
|
||||
}, {
|
||||
trigger: '.o_form_button_save:contains("Save")',
|
||||
extra_trigger: '.o_field_cell.o_data_cell.o_list_number:contains("4.00")',
|
||||
run: 'click' // SAVE Sales Order, after matrix has been applied (extra_trigger).
|
||||
}, {
|
||||
trigger: '.o_form_button_edit:contains("Edit")',
|
||||
run: 'click' // Edit Sales Order.
|
||||
trigger: '.o_form_button_edit:contains("Edit")', // Edit Sales Order.
|
||||
},
|
||||
// Ensures the matrix is opened with the values, when adding the same product.
|
||||
{
|
||||
trigger: "a:contains('Add a product')"
|
||||
}, {
|
||||
trigger: 'div[name="product_template_id"] input',
|
||||
run: function () {
|
||||
var $input = $('div[name="product_template_id"] input');
|
||||
$input.click();
|
||||
$input.val('Matrix');
|
||||
var keyDownEvent = jQuery.Event("keydown");
|
||||
keyDownEvent.which = 42;
|
||||
$input.trigger(keyDownEvent);
|
||||
}
|
||||
run: 'text Matrix'
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete a:contains("Matrix")',
|
||||
run: 'click'
|
||||
}, {
|
||||
trigger: "input[value='4']",
|
||||
run: function () {
|
||||
@@ -131,11 +116,12 @@ tour.register('sale_matrix_tour', {
|
||||
}
|
||||
}, {
|
||||
trigger: 'span:contains("Confirm")',
|
||||
run: 'click' // apply the matrix
|
||||
}, {
|
||||
trigger: '.o_sale_order',
|
||||
// wait for qty to be changed => check the total to be sure all qties are set
|
||||
extra_trigger: '.oe_subtotal_footer_separator:contains("65.32")',
|
||||
}, {
|
||||
trigger: '.o_form_button_save:contains("Save")',
|
||||
extra_trigger: '.o_field_cell.o_data_cell.o_list_number:contains("8.20")',
|
||||
run: 'click' // SAVE Sales Order, after matrix has been applied (extra_trigger).
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -2157,6 +2157,8 @@ var FieldMany2ManyBinaryMultiFiles = AbstractField.extend({
|
||||
fieldsToFetch: {
|
||||
name: {type: 'char'},
|
||||
mimetype: {type: 'char'},
|
||||
res_id: {type: 'number'},
|
||||
access_token: {type: 'char'},
|
||||
},
|
||||
events: {
|
||||
'click .o_attach': '_onAttach',
|
||||
|
||||
@@ -1548,7 +1548,7 @@
|
||||
<t t-name="FieldBinaryFileUploader.attachment_preview">
|
||||
<t t-set="url" t-value="widget.metadata[file.id] ? widget.metadata[file.id].url : false"/>
|
||||
<t t-if="file.data" t-set="file" t-value="file.data"/>
|
||||
<t t-set="editable" t-value="widget.mode === 'edit'"/>
|
||||
<t t-set="editable" t-value="widget.mode === 'edit' and !(file.res_id === 0 and file.access_token)"/>
|
||||
<t t-if="file.mimetype" t-set="mimetype" t-value="file.mimetype"/>
|
||||
<div t-attf-class="o_attachment o_attachment_many2many #{ editable ? 'o_attachment_editable' : '' } #{upload ? 'o_attachment_uploading' : ''}" t-att-title="file.name">
|
||||
<div class="o_attachment_wrap">
|
||||
|
||||
@@ -2536,11 +2536,14 @@ QUnit.module('relational_fields', {
|
||||
fields: {
|
||||
name: {string:"Name", type: "char"},
|
||||
mimetype: {string: "Mimetype", type: "char"},
|
||||
res_id: {type: "number"},
|
||||
access_token: {type: "char"},
|
||||
},
|
||||
records: [{
|
||||
id: 17,
|
||||
name: 'Marley&Me.jpg',
|
||||
mimetype: 'jpg',
|
||||
res_id: 1,
|
||||
}],
|
||||
};
|
||||
this.data.turtle.fields.picture_ids = {
|
||||
@@ -2564,7 +2567,7 @@ QUnit.module('relational_fields', {
|
||||
mockRPC: function (route, args) {
|
||||
assert.step(route);
|
||||
if (route === '/web/dataset/call_kw/ir.attachment/read') {
|
||||
assert.deepEqual(args.args[1], ['name', 'mimetype']);
|
||||
assert.deepEqual(args.args[1], ['name', 'mimetype', 'res_id', 'access_token']);
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
@@ -162,13 +162,13 @@ class Web_Editor(http.Controller):
|
||||
return True
|
||||
|
||||
@http.route('/web_editor/attachment/add_data', type='json', auth='user', methods=['POST'], website=True)
|
||||
def add_data(self, name, data, quality=0, width=0, height=0, res_id=False, res_model='ir.ui.view', **kwargs):
|
||||
def add_data(self, name, data, quality=0, width=0, height=0, res_id=False, res_model='ir.ui.view', generate_access_token=False, **kwargs):
|
||||
try:
|
||||
data = tools.image_process(data, size=(width, height), quality=quality, verify_resolution=True)
|
||||
except UserError:
|
||||
pass # not an image
|
||||
self._clean_context()
|
||||
attachment = self._attachment_create(name=name, data=data, res_id=res_id, res_model=res_model)
|
||||
attachment = self._attachment_create(name=name, data=data, res_id=res_id, res_model=res_model, generate_access_token=generate_access_token)
|
||||
return attachment._get_media_info()
|
||||
|
||||
@http.route('/web_editor/attachment/add_url', type='json', auth='user', methods=['POST'], website=True)
|
||||
@@ -240,7 +240,7 @@ class Web_Editor(http.Controller):
|
||||
'original': (attachment.original_id or attachment).read(['id', 'image_src', 'mimetype'])[0],
|
||||
}
|
||||
|
||||
def _attachment_create(self, name='', data=False, url=False, res_id=False, res_model='ir.ui.view'):
|
||||
def _attachment_create(self, name='', data=False, url=False, res_id=False, res_model='ir.ui.view', generate_access_token=False):
|
||||
"""Create and return a new attachment."""
|
||||
if name.lower().endswith('.bmp'):
|
||||
# Avoid mismatch between content type and mimetype, see commit msg
|
||||
@@ -272,6 +272,9 @@ class Web_Editor(http.Controller):
|
||||
raise UserError(_("You need to specify either data or url to create an attachment."))
|
||||
|
||||
attachment = request.env['ir.attachment'].create(attachment_data)
|
||||
if generate_access_token:
|
||||
attachment.generate_access_token()
|
||||
|
||||
return attachment
|
||||
|
||||
def _clean_context(self):
|
||||
|
||||
@@ -243,9 +243,9 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
|
||||
* when closing the wizard.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} attachments
|
||||
* @param {Object} event the event containing attachment data
|
||||
*/
|
||||
_onAttachmentChange: function (attachments) {
|
||||
_onAttachmentChange: function (event) {
|
||||
if (!this.fieldNameAttachment) {
|
||||
return;
|
||||
}
|
||||
@@ -253,7 +253,7 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
|
||||
dataPointID: this.dataPointID,
|
||||
changes: _.object([this.fieldNameAttachment], [{
|
||||
operation: 'ADD_M2M',
|
||||
ids: attachments
|
||||
ids: event.data
|
||||
}])
|
||||
});
|
||||
},
|
||||
|
||||
@@ -581,8 +581,10 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
'res_model': self.options.res_model,
|
||||
'width': 0,
|
||||
'quality': 0,
|
||||
'generate_access_token': true,
|
||||
},
|
||||
}).then(function (attachment) {
|
||||
self.trigger_up('wysiwyg_attachment', attachment);
|
||||
self._handleNewAttachment(attachment);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ var weTestUtils = require('web_editor.test_utils');
|
||||
var core = require('web.core');
|
||||
var Wysiwyg = require('web_editor.wysiwyg');
|
||||
var MediaDialog = require('wysiwyg.widgets.MediaDialog');
|
||||
var FieldHtml = require('web_editor.field.html');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
@@ -282,6 +283,90 @@ QUnit.module('web_editor', {}, function () {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('media dialog: upload', async function (assert) {
|
||||
/**
|
||||
* Ensures _onAttachmentChange from FieldHTML is called on file upload
|
||||
*/
|
||||
assert.expect(1);
|
||||
const onAttachmentChangeTriggered = testUtils.makeTestPromise();
|
||||
testUtils.mock.patch(FieldHtml, {
|
||||
'_onAttachmentChange': function (event) {
|
||||
onAttachmentChangeTriggered.resolve(true);
|
||||
}
|
||||
});
|
||||
|
||||
const form = await testUtils.createView({
|
||||
View: FormView,
|
||||
model: 'note.note',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<field name="body" widget="html" style="height: 100px"/>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.model === 'ir.attachment') {
|
||||
if (args.method === "generate_access_token") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
if (route.indexOf('/web/image/123/transparent.png') === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (route.indexOf('/web_unsplash/fetch_images') === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (route.indexOf('/web_editor/media_library_search') === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (route.indexOf('/web_editor/attachment/add_data') === 0) {
|
||||
return Promise.resolve({"id": 5, "name": "test.jpg", "description": false, "mimetype": "image/jpeg", "checksum": "7951a43bbfb08fd742224ada280913d1897b89ab",
|
||||
"url": false, "type": "binary", "res_id": 1, "res_model": "note.note", "public": false, "access_token": false,
|
||||
"image_src": "/web/image/1-a0e63e61/test.jpg", "image_width": 1, "image_height": 1, "original_id": false
|
||||
});
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
});
|
||||
await testUtils.form.clickEdit(form);
|
||||
const $field = form.$('.oe_form_field[name="body"]');
|
||||
|
||||
//init mock file data
|
||||
const fileB64 = '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iiigD//2Q==';
|
||||
const fileBytes = new Uint8Array(atob(fileB64).split('').map(char => char.charCodeAt(0)));
|
||||
|
||||
// the dialog load some xml assets
|
||||
const defMediaDialog = testUtils.makeTestPromise();
|
||||
testUtils.mock.patch(MediaDialog, {
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.opened(defMediaDialog.resolve.bind(defMediaDialog));
|
||||
this.opened(()=>{
|
||||
const input = this.activeWidget.$fileInput.get(0);
|
||||
Object.defineProperty(input, 'files', {
|
||||
value: [new File(fileBytes, "test.jpg", { type: 'image/jpeg' })],
|
||||
});
|
||||
this.activeWidget._onFileInputChange();
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
const pText = $field.find('.note-editable p').first().contents()[0];
|
||||
Wysiwyg.setRange(pText, 1);
|
||||
|
||||
await testUtils.dom.click($field.find('.note-toolbar .note-insert button:has(.fa-file-image-o)'));
|
||||
|
||||
// load static xml file (dialog, media dialog, unsplash image widget)
|
||||
await defMediaDialog;
|
||||
|
||||
assert.ok(await Promise.race([onAttachmentChangeTriggered, new Promise((res, _) => setTimeout(() => res(false), 400))]),
|
||||
"_onAttachmentChange was not called with the new attachment, necessary for unsused upload cleanup on backend");
|
||||
|
||||
testUtils.mock.unpatch(MediaDialog);
|
||||
testUtils.mock.unpatch(FieldHtml)
|
||||
form.destroy();
|
||||
|
||||
});
|
||||
|
||||
QUnit.test('media dialog: image', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user