[IMP] web: create generic controller '/web/content' and '/web/image'
Replace deprecate controllers like /web/binary/image, /web/binary/saveas... Use ETag for all content with 'unique' option to cache the content if the content is never changed.
This commit is contained in:
committed by
Antony Lesuisse
parent
5d2c3ceb02
commit
6baf611df1
@@ -113,7 +113,7 @@ var Sidebar = Widget.extend({
|
||||
render_user_avatars: function(item) {
|
||||
item.find(".oe_user_avatar").each(function() {
|
||||
var user_id = parseInt( $(this).attr('data-id'), 10);
|
||||
var url = session.url('/web/binary/image', {model: 'res.users', field: 'image_small', id: user_id});
|
||||
var url = session.url('/web/image', {model: 'res.users', field: 'image_small', id: user_id});
|
||||
$(this).attr("src", url);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ var InstantMessaging = Widget.extend({
|
||||
self.widgets = {};
|
||||
self.users = [];
|
||||
_.each(result, function(user) {
|
||||
user.image_url = session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: user.id});
|
||||
user.image_url = session.url('/web/image', {model:'res.users', field: 'image_small', id: user.id});
|
||||
var widget = new UserWidget(self, user);
|
||||
widget.appendTo(self.$(".oe_im_users"));
|
||||
widget.on("user_clicked", self, self.user_clicked);
|
||||
|
||||
@@ -13,22 +13,6 @@ import mimetypes
|
||||
class MailController(http.Controller):
|
||||
_cp_path = '/mail'
|
||||
|
||||
@http.route('/mail/download_attachment', type='http', auth='user')
|
||||
def download_attachment(self, model, id, method, attachment_id, **kw):
|
||||
# FIXME use /web/binary/saveas directly
|
||||
Model = request.registry.get(model)
|
||||
res = getattr(Model, method)(request.cr, request.uid, int(id), int(attachment_id))
|
||||
if res:
|
||||
filecontent = base64.b64decode(res.get('base64'))
|
||||
filename = res.get('filename')
|
||||
content_type = mimetypes.guess_type(filename)
|
||||
if filecontent and filename:
|
||||
return request.make_response(
|
||||
filecontent,
|
||||
headers=[('Content-Type', content_type[0] or 'application/octet-stream'),
|
||||
('Content-Disposition', content_disposition(filename))])
|
||||
return request.not_found()
|
||||
|
||||
@http.route('/mail/receive', type='json', auth='none')
|
||||
def receive(self, req):
|
||||
""" End-point to receive mail from an external SMTP server. """
|
||||
|
||||
@@ -142,22 +142,6 @@ class Message(models.Model):
|
||||
def _needaction_domain_get(self):
|
||||
return [('needaction', '=', True)]
|
||||
|
||||
#------------------------------------------------------
|
||||
# download an attachment
|
||||
#------------------------------------------------------
|
||||
|
||||
@api.multi
|
||||
def download_attachment(self, attachment_id):
|
||||
self.ensure_one()
|
||||
if attachment_id in self.attachment_ids.ids:
|
||||
attachment = self.env['ir.attachment'].sudo().browse(attachment_id)
|
||||
if attachment.datas and attachment.datas_fname:
|
||||
return {
|
||||
'base64': attachment.datas,
|
||||
'filename': attachment.datas_fname,
|
||||
}
|
||||
return False
|
||||
|
||||
#------------------------------------------------------
|
||||
# Notification API
|
||||
#------------------------------------------------------
|
||||
|
||||
@@ -253,7 +253,7 @@ var Attachment = Widget.extend ({
|
||||
for (var l in message.attachment_ids) {
|
||||
var attach = message.attachment_ids[l];
|
||||
if (!attach.formating) {
|
||||
attach.url = mail_utils.get_attachment_url(session, message.id, attach.id);
|
||||
attach.url = '/web/content/' + attach.id + '?download=true';
|
||||
attach.name = mail_utils.breakword(attach.name || attach.filename);
|
||||
attach.formating = true;
|
||||
}
|
||||
@@ -264,13 +264,6 @@ var Attachment = Widget.extend ({
|
||||
display_attachments: function (message) {
|
||||
return QWeb.render('ThreadMessageAttachments', {'record': message, 'widget': this});
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the link to resized image
|
||||
*/
|
||||
attachments_resize_image: function (id, resize) {
|
||||
return mail_utils.get_image(session, 'ir.attachment', 'datas', id, resize);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -907,11 +900,9 @@ var MailThread = Attachment.extend ({
|
||||
avt = ('/mail/static/src/img/email_icon.png');
|
||||
}
|
||||
else if (author) {
|
||||
avt = mail_utils.get_image(
|
||||
session, 'res.partner', 'image_small', author[0]);
|
||||
avt = '/web/image/res.partner/' + author[0] + '/image_small';
|
||||
}else {
|
||||
avt = mail_utils.get_image(
|
||||
session, 'res.users', 'image_small', session.uid);
|
||||
avt = '/web/image/res.partner/' + session.uid + '/image_small';
|
||||
}
|
||||
return avt;
|
||||
},
|
||||
@@ -1388,7 +1379,7 @@ var ComposeMessage = Attachment.extend ({
|
||||
'id': result.id,
|
||||
'name': result.name,
|
||||
'filename': result.filename,
|
||||
'url': mail_utils.get_attachment_url(session, this.id, result.id)
|
||||
'url': '/web/content/' + result.id + '?download=true'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ var Followers = form_common.AbstractField.extend({
|
||||
// truncate number of displayed followers
|
||||
_(this.followers).each(function (record) {
|
||||
$(qweb.render('mail_followers_partner', {
|
||||
'record': _.extend(record, {'avatar_url': mail_utils.get_image(session, record['res_model'], 'image_small', record['res_id'])}),
|
||||
'record': _.extend(record, {'avatar_url': '/web/image/' + record['res_model'] + '/' + record['res_id'] + '/image_small'}),
|
||||
'widget': self})
|
||||
).appendTo(node_user_list);
|
||||
// On mouse-enter it will show the edit_subtype pencil.
|
||||
@@ -417,4 +417,4 @@ var Followers = form_common.AbstractField.extend({
|
||||
/* Add the widget to registry */
|
||||
core.form_widget_registry.add('mail_followers', Followers);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,23 +27,6 @@ function parse_email(text) {
|
||||
return [text, false];
|
||||
}
|
||||
|
||||
/* Get an image in /web/binary/image?... */
|
||||
function get_image(session, model, field, id, resize) {
|
||||
var r = resize ? encodeURIComponent(resize) : '';
|
||||
id = id || '';
|
||||
return session.url('/web/binary/image', {model: model, field: field, id: id, resize: r});
|
||||
}
|
||||
|
||||
/* Get the url of an attachment {'id': id} */
|
||||
function get_attachment_url(session, message_id, attachment_id) {
|
||||
return session.url('/mail/download_attachment', {
|
||||
'model': 'mail.message',
|
||||
'id': message_id,
|
||||
'method': 'download_attachment',
|
||||
'attachment_id': attachment_id
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces some expressions
|
||||
* - :name - shortcut to an image
|
||||
@@ -125,8 +108,6 @@ function bindTooltipTo($el, value, position) {
|
||||
|
||||
return {
|
||||
parse_email: parse_email,
|
||||
get_image: get_image,
|
||||
get_attachment_url: get_attachment_url,
|
||||
do_replace_expressions: do_replace_expressions,
|
||||
get_text2html: get_text2html,
|
||||
expand_domain: expand_domain,
|
||||
|
||||
@@ -208,7 +208,7 @@
|
||||
<t t-if="attachment.file_type_icon === 'webimage'">
|
||||
<div t-attf-class="oe_attachment oe_preview #{attachment.upload ? 'oe_uploading' : ''}">
|
||||
<a t-att-href='attachment.url' target="_blank">
|
||||
<img t-att-src="widget.attachments_resize_image(attachment.id, [100,80])"></img>
|
||||
<img t-attf-src="/web/image/#{attachment.id}/100x80"></img>
|
||||
<div class='oe_name'><t t-raw='attachment.name'/></div>
|
||||
</a>
|
||||
<div class='oe_delete oe_e'>
|
||||
|
||||
@@ -186,11 +186,12 @@ class TestMailMessage(TestMail):
|
||||
'datas': 'My attachment'.encode('base64'),
|
||||
'name': 'doc.txt',
|
||||
'datas_fname': 'doc.txt'})
|
||||
# attach the attachment to the message
|
||||
self.message.write({'attachment_ids': [(4, attachment.id)]})
|
||||
self.message.write({'partner_ids': [(4, self.user_employee.partner_id.id)]})
|
||||
self.message.sudo(self.user_employee).read()
|
||||
# Test: Bert downloads attachment, ok because he can read message
|
||||
self.message.sudo(self.user_employee).download_attachment(attachment.id)
|
||||
# Test: Bert has access to attachment, ok because he can read message
|
||||
attachment.sudo(self.user_employee).read(['name', 'datas'])
|
||||
|
||||
def test_mail_message_access_read_author(self):
|
||||
self.message.write({'author_id': self.user_employee.partner_id.id})
|
||||
|
||||
@@ -624,7 +624,7 @@ var ProductCategoriesWidget = PosBaseWidget.extend({
|
||||
},
|
||||
|
||||
get_image_url: function(category){
|
||||
return window.location.origin + '/web/binary/image?model=pos.category&field=image_medium&id='+category.id;
|
||||
return window.location.origin + '/web/image?model=pos.category&field=image_medium&id='+category.id;
|
||||
},
|
||||
|
||||
render_category: function( category, with_image ){
|
||||
@@ -771,7 +771,7 @@ var ProductListWidget = PosBaseWidget.extend({
|
||||
this.renderElement();
|
||||
},
|
||||
get_product_image_url: function(product){
|
||||
return window.location.origin + '/web/binary/image?model=product.product&field=image_medium&id='+product.id;
|
||||
return window.location.origin + '/web/image?model=product.product&field=image_medium&id='+product.id;
|
||||
},
|
||||
replace: function($target){
|
||||
this.renderElement();
|
||||
@@ -1109,7 +1109,7 @@ var ClientListScreenWidget = ScreenWidget.extend({
|
||||
}
|
||||
},
|
||||
partner_icon_url: function(id){
|
||||
return '/web/binary/image?model=res.partner&id='+id+'&field=image_small';
|
||||
return '/web/image?model=res.partner&id='+id+'&field=image_small';
|
||||
},
|
||||
|
||||
// ui handle for the 'edit selected customer' action
|
||||
|
||||
@@ -375,7 +375,7 @@ var FloorScreenWidget = screens.ScreenWidget.extend({
|
||||
}
|
||||
},
|
||||
background_image_url: function(floor) {
|
||||
return '/web/binary/image?model=restaurant.floor&id='+floor.id+'&field=background_image';
|
||||
return '/web/image?model=restaurant.floor&id='+floor.id+'&field=background_image';
|
||||
},
|
||||
get_floor_style: function() {
|
||||
var style = "";
|
||||
|
||||
@@ -17,7 +17,7 @@ options.registry.many2one.include({
|
||||
var $img = $('.header .row img:first');
|
||||
var css = window.getComputedStyle($img[0]);
|
||||
$img.css("max-height", css.height+'px');
|
||||
$img.attr("src", "/web_editor/image/res.partner/"+self.ID+"/image");
|
||||
$img.attr("src", "/web/image/res.partner/"+self.ID+"/image");
|
||||
setTimeout(function () { $img.removeClass('o_dirty'); },0);
|
||||
}
|
||||
}
|
||||
|
||||
+139
-126
@@ -1,6 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import ast
|
||||
import base64
|
||||
import csv
|
||||
import functools
|
||||
@@ -39,7 +38,8 @@ from openerp.tools import topological_sort
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import ustr
|
||||
from openerp import http
|
||||
from openerp.http import request, serialize_exception as _serialize_exception
|
||||
import mimetypes
|
||||
from openerp.http import request, serialize_exception as _serialize_exception, STATIC_CACHE
|
||||
from openerp.exceptions import AccessError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -432,6 +432,82 @@ def content_disposition(filename):
|
||||
else:
|
||||
return "attachment; filename*=UTF-8''%s" % escaped
|
||||
|
||||
def binary_content(xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='datas_fname', download=False, mimetype=None, default_mimetype='application/octet-stream', env=None):
|
||||
""" Get file, attachment or downloadable content
|
||||
|
||||
If the ``xmlid`` and ``id`` parameter is omitted, fetches the default value for the
|
||||
binary field (via ``default_get``), otherwise fetches the field for
|
||||
that precise record.
|
||||
|
||||
:param str xmlid: xmlid of the record
|
||||
:param str model: name of the model to fetch the binary from
|
||||
:param int id: id of the record from which to fetch the binary
|
||||
:param str field: binary field
|
||||
:param bool unique: add a max-age for the cache control
|
||||
:param str filename: choose a filename
|
||||
:param str filename_field: if not create an filename with model-id-field
|
||||
:param bool download: apply headers to download the file
|
||||
:param str mimetype: mintype of the field (for headers)
|
||||
:param str default_mimetype: default mintype if no mintype found
|
||||
:param Environment env: by default use request.env
|
||||
:returns: (status, headers, content)
|
||||
"""
|
||||
env = env or request.env
|
||||
# get object and content
|
||||
obj = None
|
||||
if xmlid:
|
||||
obj = env.ref(xmlid, False)
|
||||
elif id and model in env.registry:
|
||||
obj = env[model].browse(int(id))
|
||||
|
||||
# obj exists
|
||||
if not obj or not obj.exists() or field not in obj:
|
||||
return (404, [], None)
|
||||
|
||||
# check read access
|
||||
try:
|
||||
last_update = obj['__last_update']
|
||||
except AccesError, e:
|
||||
return (403, [], None)
|
||||
status = 200
|
||||
|
||||
# filename
|
||||
if not filename:
|
||||
if filename_field in obj:
|
||||
filename = obj[filename_field]
|
||||
else:
|
||||
filename = "%s-%s-%s" % (obj._model._name, obj.id, field)
|
||||
|
||||
# mimetype
|
||||
if not mimetype:
|
||||
if 'mimetype' in obj and obj.mimetype:
|
||||
mimetype = obj.mimetype
|
||||
elif filename:
|
||||
mimetype = mimetypes.guess_type(filename)[0]
|
||||
if not mimetype:
|
||||
mimetype = default_mimetype
|
||||
headers = [('Content-Type', mimetype)]
|
||||
|
||||
# cache
|
||||
etag = hasattr(request, 'httprequest') and request.httprequest.headers.get('If-None-Match')
|
||||
retag = hashlib.md5(last_update).hexdigest()
|
||||
if etag == retag:
|
||||
status = 304
|
||||
headers.append(('ETag', retag))
|
||||
|
||||
if unique:
|
||||
headers.append(('Cache-Control', 'max-age=%s' % STATIC_CACHE))
|
||||
else:
|
||||
headers.append(('Cache-Control', 'max-age=0'))
|
||||
|
||||
# content-disposition default name
|
||||
if download:
|
||||
headers.append(('Content-Disposition', content_disposition(filename)))
|
||||
|
||||
# get content after cache control
|
||||
content = obj[field] or ''
|
||||
|
||||
return (status, headers, content)
|
||||
|
||||
#----------------------------------------------------------
|
||||
# OpenERP Web web Controllers
|
||||
@@ -978,138 +1054,74 @@ class TreeView(View):
|
||||
|
||||
class Binary(http.Controller):
|
||||
|
||||
@http.route('/web/binary/image', type='http', auth="public")
|
||||
def image(self, model, id, field, **kw):
|
||||
last_update = '__last_update'
|
||||
Model = request.registry[model]
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
headers = [('Content-Type', 'image/png')]
|
||||
etag = request.httprequest.headers.get('If-None-Match')
|
||||
hashed_session = hashlib.md5(request.session_id).hexdigest()
|
||||
retag = hashed_session
|
||||
id = None if not id else simplejson.loads(id)
|
||||
if type(id) is list:
|
||||
id = id[0] # m2o
|
||||
try:
|
||||
if etag:
|
||||
if not id and hashed_session == etag:
|
||||
return werkzeug.wrappers.Response(status=304)
|
||||
else:
|
||||
date = Model.read(cr, uid, [id], [last_update], context)[0].get(last_update)
|
||||
if hashlib.md5(date).hexdigest() == etag:
|
||||
return werkzeug.wrappers.Response(status=304)
|
||||
|
||||
if not id:
|
||||
res = Model.default_get(cr, uid, [field], context).get(field)
|
||||
image_base64 = res
|
||||
else:
|
||||
res = Model.read(cr, uid, [id], [last_update, field], context)[0]
|
||||
retag = hashlib.md5(res.get(last_update)).hexdigest()
|
||||
image_base64 = res.get(field)
|
||||
|
||||
if kw.get('resize'):
|
||||
resize = kw.get('resize').split(',')
|
||||
if len(resize) == 2 and int(resize[0]) and int(resize[1]):
|
||||
width = int(resize[0])
|
||||
height = int(resize[1])
|
||||
# resize maximum 500*500
|
||||
if width > 500: width = 500
|
||||
if height > 500: height = 500
|
||||
image_base64 = openerp.tools.image_resize_image(base64_source=image_base64, size=(width, height), encoding='base64', filetype='PNG')
|
||||
|
||||
image_data = base64.b64decode(image_base64)
|
||||
|
||||
except Exception:
|
||||
image_data = self.placeholder()
|
||||
headers.append(('ETag', retag))
|
||||
headers.append(('Content-Length', len(image_data)))
|
||||
try:
|
||||
ncache = int(kw.get('cache'))
|
||||
headers.append(('Cache-Control', 'no-cache' if ncache == 0 else 'max-age=%s' % (ncache)))
|
||||
except:
|
||||
pass
|
||||
return request.make_response(image_data, headers)
|
||||
|
||||
def placeholder(self, image='placeholder.png'):
|
||||
addons_path = http.addons_manifest['web']['addons_path']
|
||||
return open(os.path.join(addons_path, 'web', 'static', 'src', 'img', image), 'rb').read()
|
||||
|
||||
@http.route('/web/binary/saveas', type='http', auth="public")
|
||||
@serialize_exception
|
||||
def saveas(self, model, field, id=None, filename_field=None, **kw):
|
||||
""" Download link for files stored as binary fields.
|
||||
|
||||
If the ``id`` parameter is omitted, fetches the default value for the
|
||||
binary field (via ``default_get``), otherwise fetches the field for
|
||||
that precise record.
|
||||
|
||||
:param str model: name of the model to fetch the binary from
|
||||
:param str field: binary field
|
||||
:param str id: id of the record from which to fetch the binary
|
||||
:param str filename_field: field holding the file's name, if any
|
||||
:returns: :class:`werkzeug.wrappers.Response`
|
||||
"""
|
||||
Model = request.registry[model]
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
fields = [field]
|
||||
content_type = 'application/octet-stream'
|
||||
if filename_field:
|
||||
fields.append(filename_field)
|
||||
if id:
|
||||
fields.append('file_type')
|
||||
res = Model.read(cr, uid, [int(id)], fields, context)[0]
|
||||
if res.get('file_type'):
|
||||
content_type = res['file_type']
|
||||
@http.route(['/web/content',
|
||||
'/web/content/<string:xmlid>',
|
||||
'/web/content/<string:xmlid>/<string:filename>',
|
||||
'/web/content/<int:id>',
|
||||
'/web/content/<int:id>/<string:filename>',
|
||||
'/web/content/<string:model>/<int:id>/<string:field>',
|
||||
'/web/content/<string:model>/<int:id>/<string:field>/<string:filename>'], type='http', auth="public")
|
||||
def content_common(self, xmlid=None, model='ir.attachment', id=None, field='datas', filename=None, filename_field='datas_fname', unique=None, mimetype=None, download=None, data=None, token=None):
|
||||
status, headers, content = binary_content(xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, filename_field=filename_field, download=download, mimetype=mimetype)
|
||||
if status == 304:
|
||||
response = werkzeug.wrappers.Response(status=status, headers=headers)
|
||||
elif status != 200:
|
||||
response = request.not_found()
|
||||
else:
|
||||
res = Model.default_get(cr, uid, fields, context)
|
||||
filecontent = base64.b64decode(res.get(field) or '')
|
||||
if not filecontent:
|
||||
content_base64 = base64.b64decode(content)
|
||||
headers.append(('Content-Length', len(content_base64)))
|
||||
response = request.make_response(content_base64, headers)
|
||||
if token:
|
||||
response.set_cookie('fileToken', token)
|
||||
return response
|
||||
|
||||
@http.route(['/web/image',
|
||||
'/web/image/<string:xmlid>',
|
||||
'/web/image/<string:xmlid>/<string:filename>',
|
||||
'/web/image/<string:xmlid>/<int:width>x<int:height>',
|
||||
'/web/image/<string:xmlid>/<int:width>x<int:height>/<string:filename>',
|
||||
'/web/image/<int:id>',
|
||||
'/web/image/<int:id>/<string:filename>',
|
||||
'/web/image/<string:model>/<int:id>/<string:field>',
|
||||
'/web/image/<string:model>/<int:id>/<string:field>/<string:filename>',
|
||||
'/web/image/<int:id>/<int:width>x<int:height>',
|
||||
'/web/image/<int:id>/<int:width>x<int:height>/<string:filename>',
|
||||
'/web/image/<string:model>/<int:id>/<string:field>/<int:width>x<int:height>',
|
||||
'/web/image/<string:model>/<int:id>/<string:field>/<int:width>x<int:height>/<string:filename>'], type='http', auth="public")
|
||||
def content_image(self, xmlid=None, model='ir.attachment', id=None, field='datas', filename_field='datas_fname', unique=None, filename=None, mimetype=None, download=None, width=0, height=0):
|
||||
status, headers, content = binary_content(xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, filename_field=filename_field, download=download, mimetype=mimetype, default_mimetype='image/png')
|
||||
if status == 304:
|
||||
return werkzeug.wrappers.Response(status=304, headers=headers)
|
||||
elif status != 200 and download:
|
||||
return request.not_found()
|
||||
else:
|
||||
filename = '%s_%s' % (model.replace('.', '_'), id)
|
||||
if filename_field:
|
||||
filename = res.get(filename_field, '') or filename
|
||||
return request.make_response(filecontent,
|
||||
[('Content-Type', content_type),
|
||||
('Content-Disposition', content_disposition(filename))])
|
||||
|
||||
@http.route('/web/binary/saveas_ajax', type='http', auth="public")
|
||||
@serialize_exception
|
||||
def saveas_ajax(self, data, token):
|
||||
jdata = simplejson.loads(data)
|
||||
model = jdata['model']
|
||||
field = jdata['field']
|
||||
data = jdata['data']
|
||||
id = jdata.get('id', None)
|
||||
filename_field = jdata.get('filename_field', None)
|
||||
context = jdata.get('context', {})
|
||||
content_type = 'application/octet-stream'
|
||||
if content and width and height:
|
||||
# resize maximum 500*500
|
||||
if width > 500:
|
||||
width = 500
|
||||
if height > 500:
|
||||
height = 500
|
||||
content = openerp.tools.image_resize_image(base64_source=content, size=(width, height), encoding='base64', filetype='PNG')
|
||||
|
||||
image_base64 = content and base64.b64decode(content) or self.placeholder()
|
||||
headers.append(('Content-Length', len(image_base64)))
|
||||
response = request.make_response(image_base64, headers)
|
||||
response.status = str(status)
|
||||
return response
|
||||
|
||||
# backward compatibility
|
||||
@http.route(['/web/binary/image'], type='http', auth="public")
|
||||
def content_image_backward_compatibility(self, model, id, field, resize=None, **kw):
|
||||
width = None
|
||||
height = None
|
||||
if resize:
|
||||
width, height = resize.split(",")
|
||||
return self.content_image(model=model, id=id, field=field, width=width, height=height)
|
||||
|
||||
Model = request.session.model(model)
|
||||
fields = [field]
|
||||
if filename_field:
|
||||
fields.append(filename_field)
|
||||
if data:
|
||||
res = {field: data, filename_field: jdata.get('filename', None)}
|
||||
elif id:
|
||||
fields.append('file_type')
|
||||
res = Model.read([int(id)], fields, context)[0]
|
||||
if res.get('file_type'):
|
||||
content_type = res['file_type']
|
||||
else:
|
||||
res = Model.default_get(fields, context)
|
||||
filecontent = base64.b64decode(res.get(field) or '')
|
||||
if not filecontent:
|
||||
raise ValueError(_("No content found for field '%s' on '%s:%s'") %
|
||||
(field, model, id))
|
||||
else:
|
||||
filename = '%s_%s' % (model.replace('.', '_'), id)
|
||||
if filename_field:
|
||||
filename = res.get(filename_field, '') or filename
|
||||
return request.make_response(filecontent,
|
||||
headers=[('Content-Type', content_type),
|
||||
('Content-Disposition', content_disposition(filename))],
|
||||
cookies={'fileToken': token})
|
||||
|
||||
@http.route('/web/binary/upload', type='http', auth="user")
|
||||
@serialize_exception
|
||||
@@ -1145,6 +1157,7 @@ class Binary(http.Controller):
|
||||
}, request.context)
|
||||
args = {
|
||||
'filename': ufile.filename,
|
||||
'mimetype': ufile.content_type,
|
||||
'id': attachment_id
|
||||
}
|
||||
except Exception:
|
||||
|
||||
@@ -1462,7 +1462,7 @@ var FieldMany2ManyBinaryMultiFiles = AbstractManyField.extend(common.Reinitializ
|
||||
this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change );
|
||||
},
|
||||
get_file_url: function (attachment) {
|
||||
return this.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: attachment.id});
|
||||
return '/web/content/' + attachment.id + '?download=true';
|
||||
},
|
||||
read_name_values : function () {
|
||||
var self = this;
|
||||
@@ -1474,7 +1474,7 @@ var FieldMany2ManyBinaryMultiFiles = AbstractManyField.extend(common.Reinitializ
|
||||
return this.ds_file.call('read', [_value, ['id', 'name', 'datas_fname']]).then(function (datas) {
|
||||
_.each(datas, function (data) {
|
||||
data.no_unlink = true;
|
||||
data.url = self.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: data.id});
|
||||
data.url = '/web/content/' + data.id + '?download=true';
|
||||
self.data[data.id] = data;
|
||||
});
|
||||
return ids;
|
||||
|
||||
@@ -333,6 +333,7 @@ var FormView = View.extend(common.FieldManagerMixin, {
|
||||
}
|
||||
var fields = _.keys(self.fields_view.fields);
|
||||
fields.push('display_name');
|
||||
fields.push('__last_update');
|
||||
return self.dataset.read_index(fields, {
|
||||
context: { 'bin_size': true }
|
||||
}).then(function(r) {
|
||||
|
||||
@@ -1155,18 +1155,18 @@ var FieldBinary = common.AbstractField.extend(common.ReinitializeFieldMixin, {
|
||||
var filename_fieldname = this.node.attrs.filename;
|
||||
var filename_field = this.view.fields && this.view.fields[filename_fieldname];
|
||||
this.session.get_file({
|
||||
url: '/web/binary/saveas_ajax',
|
||||
data: {data: JSON.stringify({
|
||||
model: this.view.dataset.model,
|
||||
id: (this.view.datarecord.id || ''),
|
||||
field: this.name,
|
||||
filename_field: (filename_fieldname || ''),
|
||||
data: utils.is_bin_size(value) ? null : value,
|
||||
filename: filename_field ? filename_field.get('value') : null,
|
||||
context: this.view.dataset.get_context()
|
||||
})},
|
||||
complete: framework.unblockUI,
|
||||
error: c.rpc_error.bind(c)
|
||||
'url': '/web/content',
|
||||
'data': {
|
||||
'model': this.view.dataset.model,
|
||||
'id': this.view.datarecord.id,
|
||||
'field': this.name,
|
||||
'filename_field': filename_fieldname,
|
||||
'filename': filename_field ? filename_field.get('value') : null,
|
||||
'download': true,
|
||||
'data': utils.is_bin_size(value) ? null : value,
|
||||
},
|
||||
'complete': framework.unblockUI,
|
||||
'error': c.rpc_error.bind(c)
|
||||
});
|
||||
ev.stopPropagation();
|
||||
return false;
|
||||
@@ -1263,11 +1263,11 @@ var FieldBinaryImage = FieldBinary.extend({
|
||||
var field = this.name;
|
||||
if (this.options.preview_image)
|
||||
field = this.options.preview_image;
|
||||
url = session.url('/web/binary/image', {
|
||||
url = session.url('/web/image', {
|
||||
model: this.view.dataset.model,
|
||||
id: id,
|
||||
field: field,
|
||||
t: (new Date().getTime()),
|
||||
unique: (this.view.datarecord.__last_update || '').replace(/[^0-9]/g, ''),
|
||||
});
|
||||
} else {
|
||||
url = this.placeholder;
|
||||
|
||||
@@ -1910,7 +1910,7 @@ var ColumnBinary = Column.extend({
|
||||
if (value.substr(0, 10).indexOf(' ') == -1) {
|
||||
download_url = "data:application/octet-stream;base64," + value;
|
||||
} else {
|
||||
download_url = session.url('/web/binary/saveas', {model: options.model, field: this.id, id: options.id});
|
||||
download_url = session.url('/web/content', {model: options.model, field: this.id, id: options.id, download: true});
|
||||
if (this.filename) {
|
||||
download_url += '&filename_field=' + this.filename;
|
||||
}
|
||||
|
||||
@@ -174,16 +174,14 @@ var Sidebar = Widget.extend({
|
||||
}
|
||||
},
|
||||
on_attachments_loaded: function(attachments) {
|
||||
var self = this;
|
||||
var prefix = session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'name'});
|
||||
_.each(attachments,function(a) {
|
||||
a.label = a.name;
|
||||
if(a.type === "binary") {
|
||||
a.url = prefix + '&id=' + a.id + '&t=' + (new Date().getTime());
|
||||
a.url = '/web/content/' + a.id + '?download=true&t=' + (new Date().getTime());
|
||||
}
|
||||
});
|
||||
self.items.files = attachments;
|
||||
self.redraw();
|
||||
this.items.files = attachments;
|
||||
this.redraw();
|
||||
this.$('.oe_sidebar_add_attachment .oe_form_binary_file').change(this.on_attachment_changed);
|
||||
this.$el.find('.oe_sidebar_delete_item').click(this.on_attachment_delete);
|
||||
},
|
||||
|
||||
@@ -47,7 +47,7 @@ var SystrayMenu = Widget.extend({
|
||||
if (!session.debug) {
|
||||
topbar_name = _.str.sprintf("%s (%s)", topbar_name, session.db);
|
||||
}
|
||||
var avatar_src = session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: session.uid});
|
||||
var avatar_src = session.url('/web/image', {model:'res.users', field: 'image_small', id: session.uid});
|
||||
$avatar.attr('src', avatar_src);
|
||||
|
||||
core.bus.trigger('resize'); // Re-trigger the reflow logic
|
||||
|
||||
@@ -565,7 +565,7 @@ var CalendarView = View.extend({
|
||||
if (attendee_showed<= MAX_ATTENDEES) {
|
||||
if (self.avatar_model !== null) {
|
||||
the_title_avatar += '<img title="' + _.escape(self.all_attendees[the_attendee_people]) + '" class="o_attendee_head" \
|
||||
src="/web/binary/image?model=' + self.avatar_model + '&field=image_small&id=' + the_attendee_people + '"></img>';
|
||||
src="/web/image/' + self.avatar_model + '/' + the_attendee_people + '/image_small"></img>';
|
||||
}
|
||||
else {
|
||||
if (!self.colorIsAttendee || the_attendee_people != temp_ret[self.color_field]) {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<span><i t-attf-class="fa fa-users fa-fw o_cal_avatar"></i></span>
|
||||
</t>
|
||||
<t t-if="(filters_value.value != -1) && filters_value.avatar_model ">
|
||||
<img t-att-src="'/web/binary/image?model=' + filters_value.avatar_model + '&field=image_small&id=' + filters_value.value" class="o_cal_avatar"/>
|
||||
<img t-att-src="'/web/image/' + filters_value.avatar_model + '/' + filters_value.value + '/image_small" class="o_cal_avatar"/>
|
||||
</t>
|
||||
<span t-attf-class="color_filter o_underline_color_#{filters_value.color}" ><t t-esc="filters_value.label" /></span>
|
||||
</t>
|
||||
|
||||
@@ -127,66 +127,6 @@ class Web_Editor(http.Controller):
|
||||
|
||||
return response
|
||||
|
||||
#------------------------------------------------------
|
||||
# image route for browse record
|
||||
#------------------------------------------------------
|
||||
def placeholder(self, response):
|
||||
return request.registry['ir.attachment']._image_placeholder(response)
|
||||
|
||||
@http.route([
|
||||
'/web_editor/image',
|
||||
'/web_editor/image/<xmlid>',
|
||||
'/web_editor/image/<xmlid>/<int:max_width>x<int:max_height>',
|
||||
'/web_editor/image/<xmlid>/<field>',
|
||||
'/web_editor/image/<xmlid>/<field>/<int:max_width>x<int:max_height>',
|
||||
'/web_editor/image/<model>/<id>/<field>',
|
||||
'/web_editor/image/<model>/<id>/<field>/<int:max_width>x<int:max_height>'
|
||||
], type='http', auth="public")
|
||||
def image(self, model=None, id=None, field=None, xmlid=None, max_width=None, max_height=None):
|
||||
""" Fetches the requested field and ensures it does not go above
|
||||
(max_width, max_height), resizing it if necessary.
|
||||
|
||||
If the record is not found or does not have the requested field,
|
||||
returns a placeholder image via :meth:`~.placeholder`.
|
||||
|
||||
Sets and checks conditional response parameters:
|
||||
* :mailheader:`ETag` is always set (and checked)
|
||||
* :mailheader:`Last-Modified is set iif the record has a concurrency
|
||||
field (``__last_update``)
|
||||
|
||||
The requested field is assumed to be base64-encoded image data in
|
||||
all cases.
|
||||
|
||||
xmlid can be used to load the image. But the field image must by base64-encoded
|
||||
"""
|
||||
if xmlid and "." in xmlid:
|
||||
try:
|
||||
record = request.env.ref(xmlid)
|
||||
model, id = record._name, record.id
|
||||
except:
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
if model == 'ir.attachment' and not field:
|
||||
if record.sudo().type == "url":
|
||||
field = "url"
|
||||
else:
|
||||
field = "datas"
|
||||
|
||||
if not model or not id or not field:
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
try:
|
||||
idsha = str(id).split('_')
|
||||
id = idsha[0]
|
||||
response = werkzeug.wrappers.Response()
|
||||
return request.registry['ir.attachment']._image(
|
||||
request.cr, request.uid, model, id, field, response, max_width, max_height,
|
||||
cache=STATIC_CACHE if len(idsha) > 1 else None)
|
||||
except Exception:
|
||||
logger.exception("Cannot render image field %r of record %s[%s] at size(%s,%s)",
|
||||
field, model, id, max_width, max_height)
|
||||
response = werkzeug.wrappers.Response()
|
||||
return self.placeholder(response)
|
||||
|
||||
#------------------------------------------------------
|
||||
# add attachment (images or link)
|
||||
#------------------------------------------------------
|
||||
@@ -200,7 +140,6 @@ class Web_Editor(http.Controller):
|
||||
uploads = []
|
||||
message = None
|
||||
if not upload: # no image provided, storing the link and the image name
|
||||
uploads.append({'website_url': url})
|
||||
name = url.split("/").pop() # recover filename
|
||||
attachment_id = Attachments.create(request.cr, request.uid, {
|
||||
'name': name,
|
||||
@@ -208,6 +147,7 @@ class Web_Editor(http.Controller):
|
||||
'url': url,
|
||||
'res_model': 'ir.ui.view',
|
||||
}, request.context)
|
||||
uploads.append({'id': attachment_id})
|
||||
else: # images provided
|
||||
try:
|
||||
attachment_ids = []
|
||||
@@ -230,10 +170,7 @@ class Web_Editor(http.Controller):
|
||||
'res_model': 'ir.ui.view',
|
||||
}, request.context)
|
||||
attachment_ids.append(attachment_id)
|
||||
|
||||
uploads = Attachments.read(
|
||||
request.cr, request.uid, attachment_ids, ['website_url'],
|
||||
context=request.context)
|
||||
uploads = [{'id': id} for id in attachment_ids]
|
||||
except Exception, e:
|
||||
logger.exception("Failed to upload image to attachment")
|
||||
message = unicode(e)
|
||||
|
||||
@@ -28,122 +28,9 @@ class ir_attachment(osv.osv):
|
||||
if attach.url:
|
||||
result[attach.id] = attach.url
|
||||
else:
|
||||
result[attach.id] = self.image_url(cr, uid, attach, 'datas')
|
||||
result[attach.id] = '/web/image/%s?unique=%s' % (attach.id, attach.checksum)
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
'local_url': fields.function(_local_url_get, string="Attachment URL", type='char'),
|
||||
}
|
||||
|
||||
def _image_placeholder(self, response):
|
||||
# file_open may return a StringIO. StringIO can be closed but are
|
||||
# not context managers in Python 2 though that is fixed in 3
|
||||
with contextlib.closing(openerp.tools.misc.file_open(
|
||||
os.path.join('web', 'static', 'src', 'img', 'placeholder.png'),
|
||||
mode='rb')) as f:
|
||||
response.data = f.read()
|
||||
return response.make_conditional(request.httprequest)
|
||||
|
||||
def _image(self, cr, uid, model, id_or_ids, field, response, max_width=maxint, max_height=maxint, cache=None, context=None):
|
||||
""" Fetches the requested field and ensures it does not go above
|
||||
(max_width, max_height), resizing it if necessary.
|
||||
|
||||
Resizing is bypassed if the object provides a $field_big, which will
|
||||
be interpreted as a pre-resized version of the base field.
|
||||
|
||||
If the record is not found or does not have the requested field,
|
||||
returns a placeholder image via :meth:`~._image_placeholder`.
|
||||
|
||||
Sets and checks conditional response parameters:
|
||||
* :mailheader:`ETag` is always set (and checked)
|
||||
* :mailheader:`Last-Modified is set iif the record has a concurrency
|
||||
field (``__last_update``)
|
||||
|
||||
The requested field is assumed to be base64-encoded image data in
|
||||
all cases.
|
||||
"""
|
||||
Model = self.pool[model]
|
||||
ids = isinstance(id_or_ids, (list, tuple)) and id_or_ids or [int(id_or_ids)]
|
||||
ids = Model.search(cr, uid, [('id', 'in', ids)], context=context)
|
||||
|
||||
if not ids:
|
||||
return self._image_placeholder(response)
|
||||
|
||||
concurrency = '__last_update'
|
||||
[record] = Model.read(cr, openerp.SUPERUSER_ID, ids, [concurrency, field], context=context)
|
||||
|
||||
if concurrency in record:
|
||||
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
|
||||
try:
|
||||
response.last_modified = datetime.datetime.strptime(
|
||||
record[concurrency], server_format + '.%f')
|
||||
except ValueError:
|
||||
# just in case we have a timestamp without microseconds
|
||||
response.last_modified = datetime.datetime.strptime(
|
||||
record[concurrency], server_format)
|
||||
|
||||
# Field does not exist on model or field set to False
|
||||
if not record.get(field):
|
||||
# FIXME: maybe a field which does not exist should be a 404?
|
||||
return self._image_placeholder(response)
|
||||
|
||||
response.set_etag(hashlib.sha1(record[field]).hexdigest())
|
||||
response.make_conditional(request.httprequest)
|
||||
|
||||
if cache:
|
||||
response.cache_control.max_age = cache
|
||||
response.expires = int(time.time() + cache)
|
||||
|
||||
# conditional request match
|
||||
if response.status_code == 304:
|
||||
return response
|
||||
|
||||
if model == 'ir.attachment' and field == 'url' and field in record:
|
||||
path = record[field].strip('/')
|
||||
|
||||
# Check that we serve a file from within the module
|
||||
if os.path.normpath(path).startswith('..'):
|
||||
return self._image_placeholder(response)
|
||||
|
||||
# Check that the file actually exists
|
||||
path = path.split('/')
|
||||
resource = openerp.modules.get_module_resource(*path)
|
||||
if not resource:
|
||||
return self._image_placeholder(response)
|
||||
|
||||
data = open(resource, 'rb').read()
|
||||
else:
|
||||
data = record[field].decode('base64')
|
||||
image = Image.open(cStringIO.StringIO(data))
|
||||
response.mimetype = Image.MIME[image.format]
|
||||
|
||||
filename = '%s_%s.%s' % (model.replace('.', '_'), ids[0], str(image.format).lower())
|
||||
response.headers['Content-Disposition'] = 'inline; filename="%s"' % filename
|
||||
|
||||
if (not max_width) and (not max_height):
|
||||
response.data = data
|
||||
return response
|
||||
|
||||
w, h = image.size
|
||||
max_w = int(max_width) if max_width else maxint
|
||||
max_h = int(max_height) if max_height else maxint
|
||||
|
||||
if w < max_w and h < max_h:
|
||||
response.data = data
|
||||
else:
|
||||
size = (max_w, max_h)
|
||||
img = image_resize_and_sharpen(image, size, preserve_aspect_ratio=True)
|
||||
image_save_for_web(img, response.stream, format=image.format)
|
||||
# invalidate content-length computed by make_conditional as
|
||||
# writing to response.stream does not do it (as of werkzeug 0.9.3)
|
||||
del response.headers['Content-Length']
|
||||
|
||||
return response
|
||||
|
||||
def image_url(self, cr, uid, record, field, size=None, context=None):
|
||||
"""Returns a local url that points to the image field of a given browse record."""
|
||||
model = record._name
|
||||
sudo_record = record.sudo()
|
||||
id = '%s_%s' % (record.id, hashlib.sha1(sudo_record.write_date or sudo_record.create_date or '').hexdigest()[0:7])
|
||||
size = '' if size is None else '/%s' % size
|
||||
return '/web_editor/image/%s/%s/%s%s' % (model, id, field, size)
|
||||
|
||||
@@ -15,6 +15,7 @@ import urllib2
|
||||
import urlparse
|
||||
import re
|
||||
import simplejson
|
||||
import hashlib
|
||||
|
||||
import pytz
|
||||
from dateutil import parser
|
||||
@@ -329,13 +330,16 @@ class Image(orm.AbstractModel):
|
||||
classes = ' '.join(itertools.imap(escape, aclasses))
|
||||
|
||||
max_size = None
|
||||
max_width, max_height = options.get('max_width', 0), options.get('max_height', 0)
|
||||
if max_width or max_height:
|
||||
max_size = '%sx%s' % (max_width, max_height)
|
||||
|
||||
src = self.pool['ir.attachment'].image_url(cr, uid, record, field_name, max_size)
|
||||
if options.get('resize'):
|
||||
src = "%s/%s" % (src, options.get('resize'))
|
||||
max_size = options.get('resize')
|
||||
else:
|
||||
max_width, max_height = options.get('max_width', 0), options.get('max_height', 0)
|
||||
if max_width or max_height:
|
||||
max_size = '%sx%s' % (max_width, max_height)
|
||||
|
||||
sha = hashlib.sha1(record.write_date).hexdigest()[0:7]
|
||||
max_size = '' if max_size is None else '/%s' % max_size
|
||||
src = '/web/image/%s/%s/%s%s?unique=%s' % (record._name, record.id, field_name, max_size, sha)
|
||||
|
||||
img = '<img class="%s" src="%s" style="%s"/>' % (classes, src, options.get('style', ''))
|
||||
return ir_qweb.HTMLSafe(img)
|
||||
@@ -346,8 +350,8 @@ class Image(orm.AbstractModel):
|
||||
url = element.find('img').get('src')
|
||||
|
||||
url_object = urlparse.urlsplit(url)
|
||||
if url_object.path.startswith('/web_editor/image'):
|
||||
# url might be /web_editor/image/<model>/<id>[_<checksum>]/<field>[/<width>x<height>]
|
||||
if url_object.path.startswith('/web/image'):
|
||||
# url might be /web/image/<model>/<id>[_<checksum>]/<field>[/<width>x<height>]
|
||||
fragments = url_object.path.split('/')
|
||||
query = dict(urlparse.parse_qsl(url_object.query))
|
||||
model = query.get('model', fragments[3])
|
||||
|
||||
@@ -366,7 +366,7 @@ var ImageDialog = Widget.extend({
|
||||
self.file_selected(null, error || !attachments.length);
|
||||
}
|
||||
for (var i=0; i<attachments.length; i++) {
|
||||
self.file_selected(attachments[i]['local_url'] || '/web_editor/image/ir.attachment/'+attachments[i].id+'_'+(new Date().getTime())+'/datas', error);
|
||||
self.file_selected(attachments[i]['local_url'] || '/web/image/'+attachments[i].id, error);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -127,7 +127,8 @@ var KanbanRecord = Widget.extend({
|
||||
id = JSON.stringify(id);
|
||||
if (options.preview_image)
|
||||
field = options.preview_image;
|
||||
url = session.url('/web/binary/image', {model: model, field: field, id: id});
|
||||
var unique = this.record.__last_update.value.replace(/[^0-9]/g, '');
|
||||
url = session.url('/web/image', {model: model, field: field, id: id, unique: unique});
|
||||
if (cache !== undefined) {
|
||||
// Set the cache duration in seconds.
|
||||
url += '&cache=' + parseInt(cache, 10);
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
</t>
|
||||
<t t-name="KanbanAttachmentImage">
|
||||
<div class="o_kanban_attachment_image">
|
||||
<img t-att-src="'/web/binary/image?model=ir.attachment&field=datas&id=' + widget.field.raw_value[0]"/>
|
||||
<img t-att-src="'/web/image/' + widget.field.raw_value[0]"/>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
@@ -369,17 +369,3 @@ class Website(openerp.addons.web.controllers.main.Home):
|
||||
if res:
|
||||
return res
|
||||
return request.redirect('/')
|
||||
|
||||
#------------------------------------------------------
|
||||
# image route for browse record
|
||||
#------------------------------------------------------
|
||||
@http.route([
|
||||
'/website/image',
|
||||
'/website/image/<xmlid>',
|
||||
'/website/image/<xmlid>/<field>',
|
||||
'/website/image/<model>/<id>/<field>',
|
||||
'/website/image/<model>/<id>/<field>/<int:max_width>x<int:max_height>'
|
||||
], type='http', auth="public")
|
||||
def website_image(self, model=None, id=None, field=None, xmlid=None, max_width=None, max_height=None):
|
||||
logger.warning("Deprecated image controller, please use /web_editor/image/")
|
||||
return Web_Editor().image(model=model, id=id, field=field, xmlid=xmlid, max_width=max_width, max_height=max_height)
|
||||
|
||||
@@ -5,6 +5,7 @@ import math
|
||||
import unicodedata
|
||||
import re
|
||||
import urlparse
|
||||
import hashlib
|
||||
|
||||
from sys import maxint
|
||||
|
||||
@@ -122,8 +123,8 @@ _UNSLUG_RE = re.compile(r'(?:(\w{1,2}|\w[A-Za-z0-9-_]+?\w)-)?(-?\d+)(?=$|/)')
|
||||
DEFAULT_CDN_FILTERS = [
|
||||
"^/[^/]+/static/",
|
||||
"^/web/(css|js)/",
|
||||
"^/website/image/",
|
||||
"^/web_editor/image/",
|
||||
"^/web/image",
|
||||
"^/web/content",
|
||||
]
|
||||
|
||||
def unslug(s):
|
||||
@@ -558,7 +559,11 @@ class website(osv.osv):
|
||||
return self.pool['ir.attachment']._image(cr, uid, model, id, field, response, max_width=max_width, max_height=max_height, cache=cache, context=context)
|
||||
|
||||
def image_url(self, cr, uid, record, field, size=None, context=None):
|
||||
return self.pool['ir.attachment'].image_url(cr, uid, record, field, size=size, context=context)
|
||||
"""Returns a local url that points to the image field of a given browse record."""
|
||||
sudo_record = record.sudo()
|
||||
sha = hashlib.sha1(sudo_record.write_date).hexdigest()[0:7]
|
||||
size = '' if size is None else '/%s' % size
|
||||
return '/web/image/%s/%s/%s%s?unique=%s' % (record._name, record.id, field, size, sha)
|
||||
|
||||
class website_menu(osv.osv):
|
||||
_name = "website.menu"
|
||||
@@ -637,17 +642,6 @@ class ir_attachment(osv.osv):
|
||||
'website_url': fields.related("local_url", string="Attachment URL", type='char', deprecated=True), # related for backward compatibility with saas-6
|
||||
}
|
||||
|
||||
def _image(self, cr, uid, model, id_or_ids, field, response, max_width=maxint, max_height=maxint, cache=None, context=None):
|
||||
Model = self.pool[model]
|
||||
ids = isinstance(id_or_ids, (list, tuple)) and id_or_ids or [int(id_or_ids)]
|
||||
ids = Model.search(cr, uid, [('id', 'in', ids)], context=context)
|
||||
|
||||
if not ids and 'website_published' in Model._fields:
|
||||
ids = Model.search(cr, openerp.SUPERUSER_ID, [('id', 'in', ids), ('website_published', '=', True)], context=context)
|
||||
|
||||
return super(ir_attachment, self)._image(cr, openerp.SUPERUSER_ID, model, id_or_ids, field, response,
|
||||
max_width=max_width, max_height=max_height, cache=cache, context=context)
|
||||
|
||||
class res_partner(osv.osv):
|
||||
_inherit = "res.partner"
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ odoo.define('website_blog.editor', function (require) {
|
||||
var $img = $(this).find("img");
|
||||
var css = window.getComputedStyle($img[0]);
|
||||
$img.css({ width: css.width, height: css.height });
|
||||
$img.attr("src", "/web_editor/image/res.partner/"+self.ID+"/image");
|
||||
$img.attr("src", "/web/image/res.partner/"+self.ID+"/image");
|
||||
});
|
||||
setTimeout(function () { $nodes.removeClass('o_dirty'); },0);
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@ import werkzeug.wrappers
|
||||
import simplejson
|
||||
import lxml
|
||||
from urllib2 import urlopen, URLError
|
||||
import base64
|
||||
|
||||
import openerp
|
||||
from openerp import tools, _
|
||||
from openerp.addons.web import http
|
||||
from openerp.addons.web.controllers.main import binary_content
|
||||
from openerp.addons.web.http import request
|
||||
from openerp.addons.website.models.website import slug
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class WebsiteForum(http.Controller):
|
||||
@@ -455,13 +457,14 @@ class WebsiteForum(http.Controller):
|
||||
|
||||
@http.route(['/forum/user/<int:user_id>/avatar'], type='http', auth="public", website=True)
|
||||
def user_avatar(self, user_id=0, **post):
|
||||
response = werkzeug.wrappers.Response()
|
||||
User = request.env['res.users']
|
||||
Website = request.env['website']
|
||||
user = User.sudo().search([('id', '=', user_id)])
|
||||
if not user.exists() or (user_id != request.session.uid and user.karma < 1):
|
||||
return Website._image_placeholder(response)
|
||||
return Website._image('res.users', user.id, 'image', response)
|
||||
status, headers, content = binary_content(model='res.users', id=user_id, field='image', default_mimetype='image/png', env=request.env(openerp.SUPERUSER_ID))
|
||||
if status == 304:
|
||||
return werkzeug.wrappers.Response(status=304)
|
||||
image_base64 = base64.b64decode(content)
|
||||
headers.append(('Content-Length', len(image_base64)))
|
||||
response = request.make_response(image_base64, headers)
|
||||
response.status = str(status)
|
||||
return response
|
||||
|
||||
@http.route(['/forum/<model("forum.forum"):forum>/user/<int:user_id>'], type='http', auth="public", website=True)
|
||||
def open_user(self, forum, user_id=0, **post):
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<section id="discussion" class="hidden-print oe_website_portal">
|
||||
<section class="mb32 css_editable_mode_hidden hidden-print">
|
||||
<form class="o_website_chatter_form" t-attf-action="/website_mail/post/#{chatter_mode}" method="POST" t-if="website.env.user != request.env['ir.model.data'].xmlid_to_object('base.public_user') or token or sha_in">
|
||||
<img class="img pull-left img-circle" t-att-src="website.image_url(user_id.partner_id, 'image_small')" style="width: 50px; margin-right: 10px;"/>
|
||||
<img class="img pull-left img-circle" t-attf-src="/web/image/res.partner/#{user_id.partner_id.id}/image_small/50x50" style="width: 50px; margin-right: 10px;"/>
|
||||
<div class="pull-left mb32" style="width: 75%%">
|
||||
<textarea rows="4" name="message" class="form-control" placeholder="Write a message..."></textarea>
|
||||
<input type='hidden' name="res_model" t-att-value="object._model"/>
|
||||
|
||||
@@ -205,9 +205,9 @@
|
||||
</p>
|
||||
<div class="o_mg_link_content">
|
||||
<div class="col-md-2 col-sm-3 text-center" t-foreach='message.attachment_ids' t-as='attachment'>
|
||||
<a t-att-href="'/mail/download_attachment?model=mail.message&id='+str(message.id)+'&method=download_attachment&attachment_id='+str(attachment.id)" target="_blank">
|
||||
<a t-attf-href="/web/content/#{attachment.id}?download=true" target="_blank">
|
||||
<t t-if="attachment.file_type == 'webimage'">
|
||||
<img t-att-src="'/web/binary/image?model=ir.attachment&field=datas&id=' + str(attachment.id) + '&resize=100,80'"
|
||||
<img t-att-src="'/web/image/' + str(attachment.id) + '/100x80'"
|
||||
class='oe_attachment_embedded'
|
||||
t-att-title="attachment.name"/>
|
||||
</t>
|
||||
|
||||
@@ -253,7 +253,7 @@ $('.oe_website_sale').each(function () {
|
||||
$parent.find(".oe_price:first .oe_currency_value").html(price_to_str(+$(this).data('price')) );
|
||||
|
||||
var $img = $(this).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img:first, img.product_detail_img');
|
||||
$img.attr("src", "/web_editor/image/product.product/" + $(this).val() + "/image");
|
||||
$img.attr("src", "/web/image/product.product/" + $(this).val() + "/image");
|
||||
});
|
||||
|
||||
$(oe_website_sale).on('change', 'input.js_variant_change, select.js_variant_change, ul[data-attribute_value_ids]', function (ev) {
|
||||
@@ -290,7 +290,7 @@ $('.oe_website_sale').each(function () {
|
||||
|
||||
if (product_id) {
|
||||
var $img = $(this).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img:first, img.product_detail_img');
|
||||
$img.attr("src", "/web_editor/image/product.product/" + product_id + "/image");
|
||||
$img.attr("src", "/web/image/product.product/" + product_id + "/image");
|
||||
$img.parent().attr('data-oe-model', 'product.product').attr('data-oe-id', product_id)
|
||||
.data('oe-model', 'product.product').data('oe-id', product_id);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ $('.oe_website_sale #add_to_cart, .oe_website_sale #products_grid .a-submit')
|
||||
}).then(function (modal) {
|
||||
var $modal = $(modal);
|
||||
|
||||
$modal.find('img:first').attr("src", "/web_editor/image/product.product/" + product_id + "/image_medium");
|
||||
$modal.find('img:first').attr("src", "/web/image/product.product/" + product_id + "/image_medium");
|
||||
|
||||
$modal.appendTo($form)
|
||||
.modal()
|
||||
|
||||
@@ -258,7 +258,7 @@ class website_slides(http.Controller):
|
||||
|
||||
def slide_mapped_dict(slide):
|
||||
return {
|
||||
'img_src': '/web_editor/image/slide.slide/%s/image_thumb' % (slide.id),
|
||||
'img_src': '/web/image/slide.slide/%s/image_thumb' % (slide.id),
|
||||
'caption': slide.name,
|
||||
'url': slide.website_url
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
</tr><tr>
|
||||
<td style="width: 600px; vertical-align:top; text-align:center; background-color:#FFFFF; color:#414141">
|
||||
<a href="${object.website_url}" style="color: #a24689;">
|
||||
<img alt="${object.name}" src="${ctx['base_url']}/web_editor/image/slide.slide/${object.id}/image" style="height:auto; width:500px; background-color: #cccccc; margin: 16px;">
|
||||
<img alt="${object.name}" src="${ctx['base_url']}/web/image/slide.slide/${object.id}/image" style="height:auto; width:500px; background-color: #cccccc; margin: 16px;">
|
||||
</a>
|
||||
<p style="font-size:24px; font-weight:bold"><a href="${object.website_url}">${object.name}</a></p>
|
||||
<p style="color: #ccc;">Published on ${object.write_date}</p>
|
||||
@@ -94,7 +94,7 @@
|
||||
</tr><tr>
|
||||
<td style="width: 600px; vertical-align:top; text-align:center; background-color:#FFFFF; color:#414141">
|
||||
<a href="${object.website_url}" style="color: #a24689;">
|
||||
<img alt="${object.name}" src="${ctx['base_url']}/web_editor/image/slide.slide/${object.id}/image"
|
||||
<img alt="${object.name}" src="${ctx['base_url']}/web/image/slide.slide/${object.id}/image"
|
||||
style="height:auto; width:500px; background-color: #cccccc; margin: 16px;">
|
||||
</a>
|
||||
<p style="font-size:24px; font-weight:bold"><a href="${object.website_url}">${object.name}</a></p>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<div class="well well-sm">
|
||||
<div>
|
||||
<a t-attf-href="/slides/#{slug(channel)}">
|
||||
<img t-if="channel.promoted_slide_id" t-attf-src="/web_editor/image/slide.slide/#{channel.promoted_slide_id.id}/image_medium" class="img-responsive oe_slides_channel_thumbnail"/>
|
||||
<img t-if="channel.promoted_slide_id" t-attf-src="/web/image/slide.slide/#{channel.promoted_slide_id.id}/image_medium" class="img-responsive oe_slides_channel_thumbnail"/>
|
||||
<img t-if="not channel.promoted_slide_id" src="/website_slides/static/description/channel-default.jpg" class="img-responsive oe_slides_opacity"/>
|
||||
</a>
|
||||
</div>
|
||||
@@ -215,7 +215,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-5 mt16 mb16">
|
||||
<a t-attf-href="/slides/slide/#{slug(channel.promoted_slide_id)}">
|
||||
<img t-attf-src="/web_editor/image/slide.slide/#{channel.promoted_slide_id.id}/image" class="img-responsive shadow oe_slides_promote_image"/>
|
||||
<img t-attf-src="/web/image/slide.slide/#{channel.promoted_slide_id.id}/image" class="img-responsive shadow oe_slides_promote_image"/>
|
||||
<span t-if="channel.promote_strategy in ['latest', 'most_viewed', 'most_voted', 'custom']"
|
||||
class="label label-primary pull-right" style="position: absolute;right: 50px;top: 7px;"
|
||||
t-field="channel.promote_strategy"/>
|
||||
@@ -333,7 +333,7 @@
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 col-xs-12 mt16 mb16" t-foreach="slides" t-as="slide">
|
||||
<div class="well well-sm oe_slides_thumbnail_container">
|
||||
<a t-attf-href="/slides/slide/#{slug(slide)}">
|
||||
<img t-attf-src="/web_editor/image/slide.slide/#{slide.id}/image_thumb" class="img-responsive img-thumbnail oe_slides_grid_thumbnail"/>
|
||||
<img t-attf-src="/web/image/slide.slide/#{slide.id}/image_thumb" class="img-responsive img-thumbnail oe_slides_grid_thumbnail"/>
|
||||
</a>
|
||||
<span t-if="not slide.website_published" class="label label-danger" style="position: absolute;right: 20px;top: 7px;">Not Published</span>
|
||||
<a t-if="not is_public_user and slide.website_published and slide.channel_id.promote_strategy == 'custom' and slide.channel_id.promoted_slide_id.id != slide.id"
|
||||
@@ -374,7 +374,7 @@
|
||||
<meta property="og:title" t-att-content="slide.name" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" t-att-content="slide.website_url" />
|
||||
<meta property="og:image" t-attf-content="#{request.httprequest.host_url}/web_editor/image/slide.slide/#{slide.id}/image_thumb"/>
|
||||
<meta property="og:image" t-attf-content="#{request.httprequest.host_url}/web/image/slide.slide/#{slide.id}/image_thumb"/>
|
||||
<meta property="og:description" t-att-content="slide.description" />
|
||||
</t>
|
||||
</t>
|
||||
@@ -394,7 +394,7 @@
|
||||
|
||||
<div t-if="not private">
|
||||
<t t-if="slide.datas and slide.slide_type == 'infographic'">
|
||||
<img t-attf-src="/web_editor/image/slide.slide/#{slide.id}/datas" class="img-responsive" style="width:100%"/>
|
||||
<img t-attf-src="/web/image/slide.slide/#{slide.id}/datas" class="img-responsive" style="width:100%"/>
|
||||
</t>
|
||||
<div t-if="slide.slide_type in ('presentation', 'document')" class="embed-responsive embed-responsive-16by9 embed-responsive-item mb8">
|
||||
<t t-raw="slide.embed_code"/>
|
||||
@@ -544,7 +544,7 @@
|
||||
<div class="media">
|
||||
<span class="pull-left">
|
||||
<img class="img img-circle media-object"
|
||||
t-att-src="'/web_editor/image?model=res.partner&field=image_small&id='+str(user_id.partner_id.id)"
|
||||
t-att-src="'/web/image?model=res.partner&field=image_small&id='+str(user_id.partner_id.id)"
|
||||
style="width: 30px"/>
|
||||
</span>
|
||||
<div class="media-body">
|
||||
@@ -569,7 +569,7 @@
|
||||
<li t-foreach="comments" t-as="message" class="media">
|
||||
<span class="pull-left">
|
||||
<img class="media-object img img-circle"
|
||||
t-att-src="'/web_editor/image?model=mail.message&field=author_avatar&id='+str(message.id)" style="width: 30px"/>
|
||||
t-att-src="'/web/image?model=mail.message&field=author_avatar&id='+str(message.id)" style="width: 30px"/>
|
||||
</span>
|
||||
<div class="media-body">
|
||||
<t t-call="website.publish_short">
|
||||
@@ -708,7 +708,7 @@
|
||||
<template id="related_slides" name="Related Slide">
|
||||
<li class="media">
|
||||
<a class="media-left pull-left" t-attf-href="/slides/slide/#{slug(slide)}">
|
||||
<img class="media-object oe_slides_apart_small" t-attf-src="/web_editor/image/slide.slide/#{slide.id}/image_thumb"/>
|
||||
<img class="media-object oe_slides_apart_small" t-attf-src="/web/image/slide.slide/#{slide.id}/image_thumb"/>
|
||||
</a>
|
||||
<div class="media-body">
|
||||
<a t-attf-href="/slides/slide/#{slug(slide)}"><h5 class="media-heading" t-esc="slide.name"/></a>
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
<h4><t t-esc="suggest_slide.name"/></h4>
|
||||
</div>
|
||||
<span class="oe-image-thumbnail">
|
||||
<img class="img-responsive" t-attf-src="/web_editor/image/slide.slide/#{suggest_slide.id}/image_thumb" alt="suggest_slide.name"/>
|
||||
<img class="img-responsive" t-attf-src="/web/image/slide.slide/#{suggest_slide.id}/image_thumb" alt="suggest_slide.name"/>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
</div>
|
||||
<t t-if="slide.slide_type in ('presentation', 'document')">
|
||||
<div id="PDFViewerLoader" class="oe_slides_loader">
|
||||
<img class="img-responsive" t-attf-src="/web_editor/image/slide.slide/#{slide.id}/image_medium" style="margin:auto;height:100%"/>
|
||||
<img class="img-responsive" t-attf-src="/web/image/slide.slide/#{slide.id}/image_medium" style="margin:auto;height:100%"/>
|
||||
<h4 class="text-center">
|
||||
<i class="fa fa-spinner fa-spin"></i> Loading ...
|
||||
</h4>
|
||||
@@ -100,7 +100,7 @@
|
||||
<canvas id="PDFViewerCanvas" class="img-responsive" style="margin:auto;height:100%;display:none;"></canvas>
|
||||
</t>
|
||||
<t t-if="slide.slide_type in ('infographic')">
|
||||
<img t-attf-src="/web_editor/image/slide.slide/#{slide.id}/datas" class="img-responsive" style="width:100%"/>
|
||||
<img t-attf-src="/web/image/slide.slide/#{slide.id}/datas" class="img-responsive" style="width:100%"/>
|
||||
</t>
|
||||
</div>
|
||||
<!-- Fixed bottom navbar -->
|
||||
|
||||
Reference in New Issue
Block a user