[FIX] web_editor, web_unsplash, test_website: make unsplash work
Since cfe079221c, unsplash is not working anymore as that commit did not adapt
the unsplash code to that change.
task-2581567
closes odoo/odoo#76617
X-original-commit: 9a4723628e868836c7bbe8bec31ec9b6ce3f2554
Signed-off-by: Romain Derie (rde) <rde@odoo.com>
Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
@@ -13,6 +13,7 @@ and we don't want to reload the website module every time, including it's possib
|
||||
dependencies. Neither we want to add in website module some routes, views and
|
||||
models which only purpose is to run tests.""",
|
||||
'depends': [
|
||||
'web_unsplash',
|
||||
'website',
|
||||
],
|
||||
'data': [
|
||||
|
||||
@@ -3,28 +3,30 @@ odoo.define('test_website.image_upload_progress', function (require) {
|
||||
|
||||
const tour = require('web_tour.tour');
|
||||
|
||||
const setupSteps = [{
|
||||
content: "enter edit mode",
|
||||
trigger: "a[data-action=edit]"
|
||||
}, {
|
||||
content: "drop a snippet",
|
||||
trigger: "#oe_snippets .oe_snippet[name='Text - Image'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
|
||||
extra_trigger: "body.editor_enable.editor_has_snippets",
|
||||
moveTrigger: ".oe_drop_zone",
|
||||
run: "drag_and_drop #wrap",
|
||||
}, {
|
||||
content: "drop a snippet",
|
||||
trigger: "#oe_snippets .oe_snippet[name='Image Gallery'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
|
||||
extra_trigger: "body.editor_enable.editor_has_snippets",
|
||||
moveTrigger: ".oe_drop_zone",
|
||||
run: "drag_and_drop #wrap",
|
||||
}];
|
||||
|
||||
const formatErrorMsg = "format is not supported. Try with: .gif, .jpe, .jpeg, .jpg, .png, .svg";
|
||||
|
||||
tour.register('test_image_upload_progress', {
|
||||
url: '/test_image_progress',
|
||||
test: true
|
||||
}, [
|
||||
{
|
||||
content: "enter edit mode",
|
||||
trigger: "a[data-action=edit]"
|
||||
}, {
|
||||
content: "drop a snippet",
|
||||
trigger: "#oe_snippets .oe_snippet[name='Text - Image'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
|
||||
extra_trigger: "body.editor_enable.editor_has_snippets",
|
||||
moveTrigger: ".oe_drop_zone",
|
||||
run: "drag_and_drop #wrap",
|
||||
}, {
|
||||
content: "drop a snippet",
|
||||
trigger: "#oe_snippets .oe_snippet[name='Image Gallery'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
|
||||
extra_trigger: "body.editor_enable.editor_has_snippets",
|
||||
moveTrigger: ".oe_drop_zone",
|
||||
run: "drag_and_drop #wrap",
|
||||
},
|
||||
...setupSteps,
|
||||
// 1. Check multi image upload
|
||||
{
|
||||
content: "click on dropped snippet",
|
||||
@@ -158,4 +160,41 @@ tour.register('test_image_upload_progress', {
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
tour.register('test_image_upload_progress_unsplash', {
|
||||
url: '/test_image_progress',
|
||||
test: true
|
||||
}, [
|
||||
...setupSteps,
|
||||
// 1. Check multi image upload
|
||||
{
|
||||
content: "click on dropped snippet",
|
||||
trigger: "#wrap .s_image_gallery .img",
|
||||
}, {
|
||||
content: "click on replace media to open image dialog",
|
||||
trigger: 'we-customizeblock-option [data-replace-media]',
|
||||
}, {
|
||||
content: "search 'fox' images",
|
||||
trigger: ".o_we_search",
|
||||
run: "text fox",
|
||||
}, {
|
||||
content: "click on unsplash result", // note that unsplash is mocked
|
||||
trigger: ".o_unsplash_attachment_cell"
|
||||
}, {
|
||||
content: "check that the upload progress bar is correctly shown",
|
||||
// ensure it is there so we are sure next step actually test something
|
||||
extra_trigger: '.o_notification_close',
|
||||
trigger: ".o_we_progressbar:contains('fox'):contains('File has been uploaded')",
|
||||
in_modal: false,
|
||||
run: function () {}, // it's a check
|
||||
}, {
|
||||
content: "notification should close after 3 seconds",
|
||||
trigger: 'body:not(:has(.o_notification_close))',
|
||||
in_modal: false,
|
||||
}, {
|
||||
content: "unsplash image (mocked to logo) should have been used",
|
||||
trigger: "#wrap .s_image_gallery .img[data-original-src^='/unsplash/HQqIOc8oYro/fox']",
|
||||
},
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
@@ -1,11 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.web_editor.controllers.main import Web_Editor
|
||||
from odoo.addons.web_unsplash.controllers.main import Web_Unsplash
|
||||
|
||||
import odoo.tests
|
||||
|
||||
from odoo import http
|
||||
from odoo.tools import config
|
||||
|
||||
BASE_URL = "http://127.0.0.1:%s" % (config["http_port"],)
|
||||
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
class TestImageUploadProgress(odoo.tests.HttpCase):
|
||||
|
||||
def test_01_image_upload_progress(self):
|
||||
self.start_tour("/test_image_progress", 'test_image_upload_progress', login="admin")
|
||||
|
||||
def test_02_image_upload_progress_unsplash(self):
|
||||
def media_library_search(self, **params):
|
||||
return {"results": 0, "media": []}
|
||||
|
||||
def fetch_unsplash_images(self, **post):
|
||||
return {
|
||||
'total': 1434,
|
||||
'total_pages': 48,
|
||||
'results': [{
|
||||
'id': 'HQqIOc8oYro',
|
||||
'alt_description': 'brown fox sitting on green grass field during daytime',
|
||||
'urls': {
|
||||
# 'regular': 'https://images.unsplash.com/photo-1462953491269-9aff00919695?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwzMDUwOHwwfDF8c2VhcmNofDF8fGZveHxlbnwwfHx8fDE2MzEwMzIzNDE&ixlib=rb-1.2.1&q=80&w=1080',
|
||||
'regular': BASE_URL + '/website/static/src/img/phone.png',
|
||||
},
|
||||
'links': {
|
||||
# 'download_location': 'https://api.unsplash.com/photos/HQqIOc8oYro/download?ixid=MnwzMDUwOHwwfDF8c2VhcmNofDF8fGZveHxlbnwwfHx8fDE2MzEwMzIzNDE'
|
||||
'download_location': BASE_URL + '/website/static/src/img/phone.png',
|
||||
},
|
||||
'user': {
|
||||
'name': 'Mitchell Admin',
|
||||
'links': {
|
||||
'html': BASE_URL,
|
||||
},
|
||||
},
|
||||
}]
|
||||
}
|
||||
|
||||
# because not preprocessed by ControllerType metaclass
|
||||
fetch_unsplash_images.routing_type = 'json'
|
||||
Web_Unsplash.fetch_unsplash_images = http.route("/web_unsplash/fetch_images", type='json', auth="user")(fetch_unsplash_images)
|
||||
|
||||
# disable undraw, no third party should be called in tests
|
||||
media_library_search.routing_type = 'json'
|
||||
Web_Editor.media_library_search = http.route(['/web_editor/media_library_search'], type='json', auth="user", website=True)(media_library_search)
|
||||
|
||||
self.start_tour("/", 'test_image_upload_progress_unsplash', login="admin")
|
||||
|
||||
@@ -127,10 +127,6 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
'click .o_existing_attachment_remove': '_onRemoveClick',
|
||||
'click .o_load_more': '_onLoadMoreClick',
|
||||
}),
|
||||
custom_events: Object.assign({}, SearchableMediaWidget.prototype.events || {}, {
|
||||
'file_complete': '_onAttachmentUploaded',
|
||||
'upload_complete': '_onUploadCompleted',
|
||||
}),
|
||||
existingAttachmentsTemplate: undefined,
|
||||
|
||||
IMAGE_MIMETYPES: ['image/jpg', 'image/jpeg', 'image/jpe', 'image/png', 'image/svg+xml', 'image/gif'],
|
||||
@@ -205,7 +201,6 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
if (this.uploader) {
|
||||
// Prevent uploader from being destroyed with call to super so it can linger
|
||||
this.uploader.setParent(null);
|
||||
this.uploader.destroyOnClose = true;
|
||||
this.uploader.close(2000);
|
||||
}
|
||||
this._super(...arguments);
|
||||
@@ -580,18 +575,49 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async _addData() {
|
||||
const files = this.$fileInput[0].files;
|
||||
let files = this.$fileInput[0].files;
|
||||
if (!files.length) {
|
||||
// Case if the input is emptied, return resolved promise
|
||||
return;
|
||||
}
|
||||
this.uploader = new UploadProgressToast(this, {
|
||||
files: files,
|
||||
resModel: this.options.res_model,
|
||||
resId: this.options.res_id,
|
||||
isImage: this.widgetType === 'image',
|
||||
|
||||
const uploadMutex = new concurrency.Mutex();
|
||||
|
||||
// Upload the smallest file first to block the user the least possible.
|
||||
files = _.sortBy(files, 'size');
|
||||
await this._setUpProgressToast(files);
|
||||
_.each(files, (file, index) => {
|
||||
// Upload one file at a time: no need to parallel as upload is
|
||||
// limited by bandwidth.
|
||||
uploadMutex.exec(async () => {
|
||||
const dataURL = await utils.getDataURLFromFile(file);
|
||||
const attachment = await this.uploader.rpcShowProgress({
|
||||
route: '/web_editor/attachment/add_data',
|
||||
params: {
|
||||
'name': file.name,
|
||||
'data': dataURL.split(',')[1],
|
||||
'res_id': this.options.res_id,
|
||||
'res_model': this.options.res_model,
|
||||
'is_image': this.widgetType === 'image',
|
||||
'width': 0,
|
||||
'quality': 0,
|
||||
}
|
||||
}, index);
|
||||
if (!attachment.error) {
|
||||
this._handleNewAttachment(attachment);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return uploadMutex.getUnlockedDef().then(() => {
|
||||
if (!this.uploader.hasError) {
|
||||
this.uploader.close(3000);
|
||||
}
|
||||
if (!this.options.multiImages && !this.noSave) {
|
||||
this.trigger_up('save_request');
|
||||
}
|
||||
this.noSave = false;
|
||||
});
|
||||
this.uploader.appendTo(document.body);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -695,22 +721,20 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
this.numberOfAttachmentsToDisplay = this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY;
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Sets up a progress bar for every file being uploaded in a toast.
|
||||
*
|
||||
* @private
|
||||
* @param {Object[]} files
|
||||
*/
|
||||
_onAttachmentUploaded({ data: attachment }) {
|
||||
if (!attachment.error) {
|
||||
this._handleNewAttachment(attachment);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onUploadCompleted() {
|
||||
if (!this.options.multiImages && !this.noSave) {
|
||||
this.trigger_up('save_request');
|
||||
}
|
||||
this.noSave = false;
|
||||
_setUpProgressToast: async function (files) {
|
||||
this.uploader = new UploadProgressToast(this, files);
|
||||
await this.uploader.appendTo(document.body);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import Widget from "web.Widget";
|
||||
import concurrency from "web.concurrency";
|
||||
import { getDataURLFromFile } from "web.utils";
|
||||
import {qweb} from 'web.core';
|
||||
|
||||
export const UploadProgressToast = Widget.extend({
|
||||
xmlDependencies: ['/web_editor/static/src/xml/wysiwyg.xml'],
|
||||
@@ -14,62 +12,34 @@ export const UploadProgressToast = Widget.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init(parent, { files, resModel, resId, isImage }) {
|
||||
init(parent, files) {
|
||||
this._super(...arguments);
|
||||
this.resId = resId;
|
||||
this.resModel = resModel;
|
||||
this.isImage = isImage;
|
||||
|
||||
// Upload the smallest file first to block the user the least possible.
|
||||
this.files = [...files].sort((a, b) => a.size - b.size);
|
||||
this.files.forEach((file, index) => {
|
||||
file.id = index;
|
||||
if (!file.size) {
|
||||
file.displaySize = null;
|
||||
} else if (file.size < 1024) {
|
||||
file.displaySize = file.size.toFixed(2) + " bytes";
|
||||
} else if (file.size < 1048576) {
|
||||
file.displaySize = (file.size / 1024).toFixed(2) + " KB";
|
||||
} else {
|
||||
file.displaySize = (file.size / 1048576).toFixed(2) + " MB";
|
||||
}
|
||||
});
|
||||
this.files = files;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start() {
|
||||
this.hasError = false;
|
||||
const uploadMutex = new concurrency.Mutex();
|
||||
this.files.forEach((file, index) => {
|
||||
// Upload one file at a time: no need to parallel as upload is
|
||||
// limited by bandwidth.
|
||||
uploadMutex.exec(async () => {
|
||||
const dataURL = await getDataURLFromFile(file);
|
||||
const attachment = await this._rpcShowProgress({
|
||||
route: '/web_editor/attachment/add_data',
|
||||
params: {
|
||||
name: file.name,
|
||||
data: dataURL.split(',')[1],
|
||||
res_id: this.resId,
|
||||
res_model: this.resModel,
|
||||
is_image: this.isImage,
|
||||
width: 0,
|
||||
quality: 0,
|
||||
}
|
||||
}, this.$el.find(`.js_progressbar_${index}`));
|
||||
if (!attachment.error) {
|
||||
this.trigger_up('file_complete', attachment);
|
||||
}
|
||||
});
|
||||
});
|
||||
this.$progress = $('<div/>');
|
||||
_.each(this.files, (file, index) => {
|
||||
let fileSize = file.size;
|
||||
if (!fileSize) {
|
||||
fileSize = null;
|
||||
} else if (fileSize < 1024) {
|
||||
fileSize = fileSize.toFixed(2) + " bytes";
|
||||
} else if (fileSize < 1048576) {
|
||||
fileSize = (fileSize / 1024).toFixed(2) + " KB";
|
||||
} else {
|
||||
fileSize = (fileSize / 1048576).toFixed(2) + " MB";
|
||||
}
|
||||
|
||||
uploadMutex.getUnlockedDef().then(() => {
|
||||
this.trigger_up('upload_complete');
|
||||
this.destroyOnClose = true;
|
||||
this.close(2000);
|
||||
this.$progress.append(qweb.render('wysiwyg.widgets.upload.progressbar', {
|
||||
fileId: index,
|
||||
fileName: file.name,
|
||||
fileSize: fileSize,
|
||||
}));
|
||||
});
|
||||
this.uploadMutex = uploadMutex;
|
||||
this.$el.find('.o_notification_content').append(this.$progress);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -84,32 +54,21 @@ export const UploadProgressToast = Widget.extend({
|
||||
*/
|
||||
close(delay = 0) {
|
||||
window.setTimeout(() => {
|
||||
if (!this.hasError || delay === 0) {
|
||||
this.el.querySelector('.fade').classList.remove('show');
|
||||
window.setTimeout(() => {
|
||||
if (this.destroyOnClose) {
|
||||
this.destroy();
|
||||
} else {
|
||||
this.el.classList.add('d-none');
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
this.el.querySelector('.fade').classList.remove('show');
|
||||
window.setTimeout(() => {
|
||||
this.destroy();
|
||||
}, 150);
|
||||
}, delay);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Calls a RPC and shows its progress status.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} params regular `_rpc()` parameters
|
||||
* @param {jQuery} $progressBar the element holding the progress bar
|
||||
* @returns {Promise} resolved when the RPC is complete
|
||||
* @param {integer} index file index to retrieve its related progress bar
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async _rpcShowProgress(params, $progressBar) {
|
||||
async rpcShowProgress(params, index) {
|
||||
let $progressBar = this.$el.find(`.js_progressbar_${index}`);
|
||||
try {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.upload.addEventListener('progress', ev => {
|
||||
@@ -122,7 +81,7 @@ export const UploadProgressToast = Widget.extend({
|
||||
// Don't show yet success as backend code only starts now
|
||||
$progressBar.find('.progress-bar').css({width: '100%'}).text('100%');
|
||||
});
|
||||
const attachment = await this._rpc(params, { xhr });
|
||||
const attachment = await this._rpc(params, {xhr});
|
||||
$progressBar.find('.fa-spinner, .progress').addClass('d-none');
|
||||
if (attachment.error) {
|
||||
this.hasError = true;
|
||||
|
||||
@@ -270,6 +270,23 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="wysiwyg.widgets.upload.progressbar">
|
||||
<div t-attf-class="o_we_progressbar js_progressbar_#{fileId}">
|
||||
<div class="d-flex text-info">
|
||||
<span><i class="fa fa-spinner fa-pulse"/></span>
|
||||
<span class="font-italic font-weight-bold text-truncate flex-grow-1 mx-2" t-esc="fileName"/>
|
||||
<span class="text-nowrap" t-esc="fileSize"/>
|
||||
</div>
|
||||
<div class='js_progressbar_txt'>
|
||||
<span class="text-success d-none"><i class='fa fa-check'/> File has been uploaded</span>
|
||||
<span class="text-danger d-none"><i class='fa fa-times float-left m-1'/> <span class="o_we_error_text">File could not be saved</span></span>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-info progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%;">0%</div>
|
||||
</div>
|
||||
<hr/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="wysiwyg.widgets.upload.progress_toast">
|
||||
<div class="o_notification_manager">
|
||||
<div t-attf-class="o_notification bg-info fade show" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
@@ -278,25 +295,7 @@
|
||||
aria-label="Close">
|
||||
<span class="d-inline" aria-hidden="true">×</span>
|
||||
</button>
|
||||
<div class="mr-auto o_notification_content">
|
||||
<div>
|
||||
<div t-foreach="widget.files" t-as="file" t-attf-class="o_we_progressbar js_progressbar_#{file.id}">
|
||||
<div class="d-flex text-info">
|
||||
<span><i class="fa fa-spinner fa-pulse"/></span>
|
||||
<span class="font-italic font-weight-bold text-truncate flex-grow-1 mx-2" t-esc="file.name"/>
|
||||
<span class="text-nowrap" t-esc="file.displaySize"/>
|
||||
</div>
|
||||
<div class='js_progressbar_txt'>
|
||||
<span class="text-success d-none"><i class='fa fa-check'/> File has been uploaded</span>
|
||||
<span class="text-danger d-none"><i class='fa fa-times float-left m-1'/><span class="o_we_error_text">File could not be saved</span></span>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-info progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%;">0%</div>
|
||||
</div>
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mr-auto o_notification_content"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,7 @@ class Web_Unsplash(http.Controller):
|
||||
view counter.
|
||||
'''
|
||||
try:
|
||||
if not url.startswith('https://api.unsplash.com/photos/'):
|
||||
if not url.startswith('https://api.unsplash.com/photos/') and not request.env.registry.in_test_mode():
|
||||
raise Exception(_("ERROR: Unknown Unsplash notify URL!"))
|
||||
access_key = self._get_access_key()
|
||||
requests.get(url, params=url_encode({'client_id': access_key}))
|
||||
@@ -81,9 +81,10 @@ class Web_Unsplash(http.Controller):
|
||||
for key, value in unsplashurls.items():
|
||||
url = value.get('url')
|
||||
try:
|
||||
if not url.startswith('https://images.unsplash.com/'):
|
||||
if not url.startswith('https://images.unsplash.com/') and not request.env.registry.in_test_mode():
|
||||
logger.exception("ERROR: Unknown Unsplash URL!: " + url)
|
||||
raise Exception(_("ERROR: Unknown Unsplash URL!"))
|
||||
|
||||
req = requests.get(url)
|
||||
if req.status_code != requests.codes.ok:
|
||||
continue
|
||||
|
||||
@@ -3,6 +3,7 @@ odoo.define('web_unsplash.image_widgets', function (require) {
|
||||
|
||||
var core = require('web.core');
|
||||
var UnsplashAPI = require('unsplash.api');
|
||||
const {UploadProgressToast} = require('@web_editor/js/wysiwyg/widgets/upload_progress_toast');
|
||||
var widgetsMedia = require('wysiwyg.widgets.media');
|
||||
const {_t} = require('web.core');
|
||||
|
||||
@@ -91,7 +92,7 @@ widgetsMedia.ImageWidget.include({
|
||||
_.str.sprintf(_t("Uploading '%s' image."), this._unsplash.query),
|
||||
size: null,
|
||||
}]);
|
||||
const images = await this._rpcShowProgress({
|
||||
const images = await this.uploader.rpcShowProgress({
|
||||
route: '/web_unsplash/attachment/add',
|
||||
params: {
|
||||
unsplashurls: selectedImages,
|
||||
@@ -100,7 +101,7 @@ widgetsMedia.ImageWidget.include({
|
||||
query: this._unsplash.query,
|
||||
},
|
||||
}, 0);
|
||||
this._closeProgressToast();
|
||||
this.uploader.close(3000);
|
||||
this.attachments.push(...images);
|
||||
this.selectedAttachments.push(...images);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user