summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRomain Derie <rde@odoo.com>2018-10-02 09:57:55 +0000
committerRomain Derie <rde@odoo.com>2018-10-26 10:13:07 +0000
commit40c6bc3ad21233465a72c08fff3f7951840cd47e (patch)
tree90acbae05115faeaef499863a1e9503be07cb5f5
parent8c9174ff80cf7d6be1e212935d5b7e26b737d5cd (diff)
[IMP] web_unsplash: Implement Beacon Alternative and credit Unsplash
Unsplash API requirements does not directly allow to store their images on our server (We should use hotlinking unless the image is edited (cropped etc)). Still, they have the `Image Beacon Alternative` to allow to store their images on our server if we really need it with our use case. With the Beacon Alternative, we should ping Unsplash server to notify an image view. To do so, we need to be able to recognize Unsplash imagesin the DOM, this is why we are now using /unsplash/ in the URL. The Unsplash image name should be the image ID as it will be the identifier needed to ping their server. This commit also adds credit to Unsplash in the media dialog next to the unsplash input search. It also add the `w-100` on the media dialog card as we lost that UI behavior from BS3 to BS4. opw-35060 closes odoo/odoo#27377
-rw-r--r--addons/web_editor/static/src/xml/editor.xml2
-rw-r--r--addons/web_unsplash/controllers/main.py40
-rw-r--r--addons/web_unsplash/static/src/js/unsplash_beacon.js26
-rw-r--r--addons/web_unsplash/static/src/js/unsplash_image_widget.js40
-rw-r--r--addons/web_unsplash/static/src/scss/unsplash.scss23
-rw-r--r--addons/web_unsplash/static/src/xml/unsplash_image_widget.xml73
-rw-r--r--addons/web_unsplash/views/res_config_settings_view.xml18
-rw-r--r--addons/web_unsplash/views/web_unsplash_templates.xml7
8 files changed, 140 insertions, 89 deletions
diff --git a/addons/web_editor/static/src/xml/editor.xml b/addons/web_editor/static/src/xml/editor.xml
index ac5cbe6d07b..85cf336130d 100644
--- a/addons/web_editor/static/src/xml/editor.xml
+++ b/addons/web_editor/static/src/xml/editor.xml
@@ -90,7 +90,7 @@
<input type="hidden" name="filters" t-att-value="widget.firstFilters.join('_')"/>
<input t-if="widget.options.res_id" type="hidden" name="res_id" t-att-value="widget.options.res_id"/>
<input t-if="widget.options.res_model" type="hidden" name="res_model" t-att-value="widget.options.res_model"/>
- <div class="card">
+ <div class="o_media_image_card card w-100">
<div class="card-body p-0">
<div class="form-inline">
<input type="file" class="d-none" name="upload" t-att-accept="widget.accept" multiple="multiple"/>
diff --git a/addons/web_unsplash/controllers/main.py b/addons/web_unsplash/controllers/main.py
index 0c318192bdc..c53c03ba5bf 100644
--- a/addons/web_unsplash/controllers/main.py
+++ b/addons/web_unsplash/controllers/main.py
@@ -7,7 +7,7 @@ import requests
import werkzeug.utils
from PIL import Image
-from odoo import http, tools
+from odoo import http, tools, _
from odoo.http import request
logger = logging.getLogger(__name__)
@@ -39,9 +39,12 @@ class Web_Unsplash(http.Controller):
else:
res_id = None
- for key in unsplashurls:
- url = unsplashurls[key].get('url')
+ for key, value in unsplashurls.items():
+ url = value.get('url')
try:
+ if not url.startswith('https://images.unsplash.com/'):
+ 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
@@ -56,20 +59,19 @@ class Web_Unsplash(http.Controller):
logger.exception("Timeout: " + str(e))
continue
- image = Image.open(io.BytesIO(datas))
- image_format = image.format
+ name = key
# optimized image before save
- if image_format and image_format in ('PNG', 'JPEG'):
- datas = tools.image_save_for_web(image)
-
- name = key
- # append image extension in name
- if image_format:
- name += '.' + image_format.lower()
+ if mimetype in ('image/jpeg', 'image/png'):
+ image = Image.open(io.BytesIO(datas))
+ if image.format in ('PNG', 'JPEG'):
+ datas = tools.image_save_for_web(image)
+ # append image extension in name
+ name += '.' + image.format
attachment = Attachments.create({
'name': name,
+ 'url': '/unsplash/' + name,
'datas_fname': name,
'mimetype': mimetype,
'datas': base64.b64encode(datas),
@@ -78,7 +80,7 @@ class Web_Unsplash(http.Controller):
'res_model': res_model,
})
attachment.generate_access_token()
- uploads.extend(attachment.read(['name', 'mimetype', 'checksum', 'res_id', 'res_model', 'access_token']))
+ uploads.extend(attachment.read(['name', 'mimetype', 'checksum', 'res_id', 'res_model', 'access_token', 'url']))
return uploads
@@ -88,8 +90,14 @@ class Web_Unsplash(http.Controller):
return request.env['ir.config_parameter'].sudo().get_param('unsplash.access_key')
raise werkzeug.exceptions.NotFound()
- @http.route("/web_unsplash/set_client_id", type='json', auth="user")
- def set_unsplash_client_id(self, key, **post):
+ @http.route("/web_unsplash/get_app_id", type='json', auth="public")
+ def get_unsplash_app_id(self, **post):
+ return request.env['ir.config_parameter'].sudo().get_param('unsplash.app_id')
+
+ @http.route("/web_unsplash/save_unsplash", type='json', auth="user")
+ def save_unsplash(self, **post):
if request.env.user._has_unsplash_key_rights():
- return request.env['ir.config_parameter'].sudo().set_param('unsplash.access_key', key)
+ request.env['ir.config_parameter'].sudo().set_param('unsplash.app_id', post.get('appId'))
+ request.env['ir.config_parameter'].sudo().set_param('unsplash.access_key', post.get('key'))
+ return True
raise werkzeug.exceptions.NotFound()
diff --git a/addons/web_unsplash/static/src/js/unsplash_beacon.js b/addons/web_unsplash/static/src/js/unsplash_beacon.js
new file mode 100644
index 00000000000..d287a3dd80d
--- /dev/null
+++ b/addons/web_unsplash/static/src/js/unsplash_beacon.js
@@ -0,0 +1,26 @@
+odoo.define('web_unsplash.beacon', function (require) {
+'use strict';
+
+var base = require('web_editor.base');
+var ajax = require('web.ajax');
+
+base.ready().then(function () {
+ var unsplash_images = [];
+ _.each($('img[src*="/unsplash/"]'), function (img, index) {
+ // get image id from URL (`/unsplash/xYdf5feoI.jpg` -> `xYdf5feoI`)
+ unsplash_images.push(img.src.split('/unsplash/')[1].split('.')[0]);
+ });
+ if (unsplash_images) {
+ ajax.jsonRpc('/web_unsplash/get_app_id', 'call').then(function (appID) {
+ if (appID) {
+ var unsplash_view_url = 'https://views.unsplash.com/v';
+ $.get(unsplash_view_url, {
+ photo_id: unsplash_images.join(','),
+ app_id: appID,
+ });
+ }
+ });
+ }
+});
+
+});
diff --git a/addons/web_unsplash/static/src/js/unsplash_image_widget.js b/addons/web_unsplash/static/src/js/unsplash_image_widget.js
index 7ba5f4961da..d353694ddea 100644
--- a/addons/web_unsplash/static/src/js/unsplash_image_widget.js
+++ b/addons/web_unsplash/static/src/js/unsplash_image_widget.js
@@ -17,7 +17,7 @@ ImageWidget.include({
'input input.unsplash_search': '_onChangeUnsplashSearch',
'dblclick .unsplash_img_container [data-imgid]': '_onUnsplashImgDblClick',
'click .unsplash_img_container [data-imgid]': '_onUnsplashImgClick',
- 'click button.access_key': '_onSetAccessKey',
+ 'click button.save_unsplash': '_onSaveUnsplash',
}),
/**
* @override
@@ -84,7 +84,7 @@ ImageWidget.include({
route: '/web_unsplash/attachment/add',
params: {
unsplashurls: self._unsplash.selectedImages,
- res_model : self.options.res_model || "ir.ui.view",
+ res_model : self.options.res_model,
res_id: self.options.res_id,
}
}).then(function (images) {
@@ -93,7 +93,7 @@ ImageWidget.include({
}
_.each(images, function (image) {
- image.src = _.str.sprintf('/web/image/%s', image.id);
+ image.src = image.url;
image.isDocument = !(/gif|jpe|jpg|png/.test(image.mimetype));
});
self.images = images;
@@ -130,7 +130,7 @@ ImageWidget.include({
if (!this._unsplash.query) {
return this._super.apply(this, arguments);
}
- this.unsplashAPI.getImages(self._unsplash.query, this.IMAGES_PER_PAGE, this.page).then(function (res) {
+ this.unsplashAPI.getImages(this._unsplash.query, this.IMAGES_PER_PAGE, this.page).then(function (res) {
self._unsplash.isMaxed = res.isMaxed;
var rows = _(res.images).chain()
.groupBy(function (a, index) { return Math.floor(index / self.IMAGES_PER_ROW); })
@@ -161,19 +161,29 @@ ImageWidget.include({
/**
* @private
*/
- _onSetAccessKey: function () {
+ _onSaveUnsplash: function () {
var self = this;
var key = this.$('#accessKeyInput').val().trim();
- if (key) {
- this._rpc({
- route: '/web_unsplash/set_client_id',
- params: {
- 'key': key,
- },
- }).then(function () {
- self.unsplashAPI.clientId = key;
- self._renderImages();
- });
+ var appId = this.$('#appIdInput').val().trim();
+
+ this.$('#accessKeyInput').toggleClass('is-invalid', !key);
+ this.$('#appIdInput').toggleClass('is-invalid', !appId);
+
+ if (key && appId) {
+ var params = {
+ 'key': key,
+ 'appId': appId,
+ };
+
+ if (!this.$el.find('.is-invalid').length) {
+ this._rpc({
+ route: '/web_unsplash/save_unsplash',
+ params: params,
+ }).then(function () {
+ self.unsplashAPI.clientId = key;
+ self._renderImages();
+ });
+ }
}
},
/**
diff --git a/addons/web_unsplash/static/src/scss/unsplash.scss b/addons/web_unsplash/static/src/scss/unsplash.scss
index 71e97b362b7..39b8bdde40c 100644
--- a/addons/web_unsplash/static/src/scss/unsplash.scss
+++ b/addons/web_unsplash/static/src/scss/unsplash.scss
@@ -29,24 +29,17 @@
.not-found {
font-size: 25px;
- padding: 50px 0;
+ padding: 30px 0;
}
.unsplash_error {
- padding: 50px 0;
- .title {
- font-size: 25px;
- text-transform: uppercase;
- }
- .details {
- font-size: 20px;
- .access_key_box {
- padding: 9px;
- background-color: #fcfcfc;
- border: 1px solid #ededee;
- #accessKeyInput {
- width: 300px;
- }
+ padding: 30px 0;
+ .access_key_box {
+ padding: 9px;
+ background-color: #fcfcfc;
+ border: 1px solid #ededee;
+ input {
+ min-width: 300px;
}
}
}
diff --git a/addons/web_unsplash/static/src/xml/unsplash_image_widget.xml b/addons/web_unsplash/static/src/xml/unsplash_image_widget.xml
index e1104b6014f..6514297af23 100644
--- a/addons/web_unsplash/static/src/xml/unsplash_image_widget.xml
+++ b/addons/web_unsplash/static/src/xml/unsplash_image_widget.xml
@@ -2,10 +2,10 @@
<templates id="template" xml:space="preserve">
<t t-extend="web_editor.dialog.image">
- <t t-jquery=".card-body .form-inline" t-operation="append">
- <div class="ml8">
- <span class="text-muted">— or —</span>
- <input type="text" class="form-control unsplash_search mr8" placeholder="Search from Unsplash" style="width: 250px;"/>
+ <t t-jquery=".o_media_image_card .form-inline" t-operation="append">
+ <div class="ml-auto border-left pl-4">
+ <span class="d-block font-weight-bold">Photos by <a href="https://unsplash.com" target="_blank">Unsplash</a></span>
+ <input type="text" class="form-control unsplash_search mr8" placeholder="Search free high-resolution photos" style="width: 250px;"/>
</div>
</t>
<t t-jquery=".existing-attachments" t-operation="after">
@@ -29,49 +29,56 @@
</div>
</t>
+<t t-name="web_unsplash.dialog.error.credentials">
+ <h4 class="text-muted">
+ <t t-esc="title"/>
+ </h4>
+ <div class="details">
+ <t t-esc="subtitle"/>
+ <div class="form-group btn-group mt-4 access_key_box">
+ <input type="text" class="form-control w-100" id="accessKeyInput" placeholder="Paste your access key here"/>
+ </div>
+ <a href="https://www.odoo.com/documentation/user/unsplash_access_key.html" target="_blank"><i class="fa fa-arrow-right"/> Generate an access key</a>
+ <div class="form-group btn-group mt-4 access_key_box">
+ <input type="text" class="form-control w-100" id="appIdInput" placeholder="Paste your application ID here"/>
+ </div>
+ <a href="https://www.odoo.com/documentation/user/unsplash_application_id.html" target="_blank"><i class="fa fa-arrow-right"/> How to find my Unsplash Application ID?</a>
+ <button type="button" class="btn btn-primary btn-block mt-4 save_unsplash">Apply</button>
+ </div>
+</t>
+
<t t-name="web_unsplash.dialog.error.content">
<div class="row mt16 text-muted unsplash_error">
<div class="m-auto text-center">
<t t-if="key_not_found">
- <p class="title">
- Access key is not set
- </p>
- <div class="details">
- <a href="https://unsplash.com/oauth/applications" target="_blank"><i class="fa fa-arrow-right"/> Generate an access key</a>
- <div>
- <div class="form-group btn-group access_key_box">
- <input type="text" class="form-control float-left" id="accessKeyInput" placeholder="Paste your access key here"/>
- <button type="button" class="btn btn-primary access_key">Apply</button>
- </div>
- </div>
- </div>
+ <t t-call="web_unsplash.dialog.error.credentials">
+ <t t-set="title">
+ Unsplash requires an access key and an application ID
+ </t>
+ </t>
</t>
<t t-elif="status == 403">
- <p class="title">
- Search is temporary unavailable
- </p>
+ <h4 class="text-muted">
+ Search is temporarily unavailable
+ </h4>
<div class="details">
The max number of searches is exceeded. Please retry in an hour or extend to a better account.
</div>
</t>
<t t-elif="status == 401">
- <p class="title">
- Unauthorized Key
- </p>
- <div class="details">
- Please check your unsplash api key.
- <div>
- <div class="form-group btn-group mt16 access_key_box">
- <input type="text" class="form-control float-left" id="accessKeyInput" placeholder="Paste your access key here"/>
- <button type="button" class="btn btn-primary access_key">Add</button>
- </div>
- </div>
- </div>
+ <t t-call="web_unsplash.dialog.error.credentials">
+ <t t-set="title">
+ Unauthorized Key
+ </t>
+ <t t-set="subtitle">
+ Please check your Unsplash access key and application ID.
+ </t>
+ </t>
</t>
<t t-else="">
- <p class="title">
+ <h4 class="text-muted">
Something went wrong
- </p>
+ </h4>
<div class="details">
Please check your internet connection or contact administrator.
</div>
diff --git a/addons/web_unsplash/views/res_config_settings_view.xml b/addons/web_unsplash/views/res_config_settings_view.xml
index 1aca1e2d747..2952cca50c4 100644
--- a/addons/web_unsplash/views/res_config_settings_view.xml
+++ b/addons/web_unsplash/views/res_config_settings_view.xml
@@ -6,14 +6,16 @@
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
<field name="arch" type="xml">
<div id="web_unsplash_settings" position="inside">
- <div class="content-group" attrs="{'invisible': [('module_web_unsplash', '=', False)]}">
- <label for="unsplash_access_key" class="o_light_label"/>
- <field name="unsplash_access_key"/>
- </div>
- <div>
- <a href="https://unsplash.com/oauth/applications" class="oe_link" target="_blank">
- <i class="fa fa-arrow-right"/> Generate an Access Key
- </a>
+ <div attrs="{'invisible': [('module_web_unsplash', '=', False)]}">
+ <div class="content-group mt16">
+ <label for="unsplash_access_key" class="o_light_label"/>
+ <field name="unsplash_access_key"/>
+ </div>
+ <div>
+ <a href="https://www.odoo.com/documentation/user/unsplash_access_key.html" class="oe_link" target="_blank">
+ <i class="fa fa-arrow-right"/> Generate an Access Key
+ </a>
+ </div>
</div>
</div>
</field>
diff --git a/addons/web_unsplash/views/web_unsplash_templates.xml b/addons/web_unsplash/views/web_unsplash_templates.xml
index 33a95d8baa0..71951874998 100644
--- a/addons/web_unsplash/views/web_unsplash_templates.xml
+++ b/addons/web_unsplash/views/web_unsplash_templates.xml
@@ -1,10 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
- <template id="web_unsplash_assets" inherit_id="web_editor.assets_editor" name="Unsplash Images">
+ <template id="web_unsplash_assets" inherit_id="web_editor.assets_editor" name="Unsplash Images Editor Assets">
<xpath expr="." position="inside">
<link rel="stylesheet" type="text/scss" href="/web_unsplash/static/src/scss/unsplash.scss"/>
<script type="text/javascript" src="/web_unsplash/static/src/js/unsplashapi.js"></script>
<script type="text/javascript" src="/web_unsplash/static/src/js/unsplash_image_widget.js"></script>
</xpath>
</template>
+ <template id="assets_common" inherit_id="web_editor.assets_common" name="Unsplash Images Common Assets">
+ <xpath expr="." position="inside">
+ <script type="text/javascript" src="/web_unsplash/static/src/js/unsplash_beacon.js"></script>
+ </xpath>
+ </template>
</odoo>