[PATCH] Upstream patch - 18092022

This commit is contained in:
Parthiv Patel
2022-09-18 08:35:13 +00:00
parent 19ab14268b
commit de411e5fa0
12 changed files with 171 additions and 62 deletions

View File

@@ -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

View File

@@ -1,2 +1,3 @@
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models

View File

@@ -0,0 +1 @@
from . import ir_ui_view

View 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."))

View File

@@ -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).
},
]);

View File

@@ -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',

View File

@@ -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">

View File

@@ -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);
},

View File

@@ -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):

View File

@@ -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
}])
});
},

View File

@@ -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);
});
});

View File

@@ -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);