From 2c13c1ff053da442f2d12eb450623430b17b6bff Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 1 Apr 2016 17:09:54 +0200 Subject: [PATCH 1/6] [FIX] res.users: better compatibility of read() override with new API --- openerp/addons/base/res/res_users.py | 30 +++++++++++++--------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index da850eb11ed..703462e3092 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -289,14 +289,21 @@ class res_users(osv.osv): # User can read a few of his own fields SELF_READABLE_FIELDS = ['signature', 'company_id', 'login', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz', 'tz_offset', 'groups_id', 'partner_id', '__last_update', 'action_id'] - def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'): - def override_password(o): - if ('id' not in o or o['id'] != uid): + @api.multi + def _read_from_database(self, field_names, inherited_field_names=[]): + super(res_users, self)._read_from_database(field_names, inherited_field_names) + canwrite = self.check_access_rights('write', raise_exception=False) + if not canwrite and set(USER_PRIVATE_FIELDS).intersection(field_names): + for record in self: for f in USER_PRIVATE_FIELDS: - if f in o: - o[f] = '********' - return o + try: + record._cache[f] + record._cache[f] = '********' + except Exception: + # skip SpecialValue (e.g. for missing record or access right) + pass + def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'): if fields and (ids == [uid] or ids == uid): for key in fields: if not (key in self.SELF_READABLE_FIELDS or key.startswith('context_')): @@ -304,16 +311,7 @@ class res_users(osv.osv): else: # safe fields only, so we read as super-user to bypass access rights uid = SUPERUSER_ID - - result = super(res_users, self).read(cr, uid, ids, fields=fields, context=context, load=load) - canwrite = self.pool['ir.model.access'].check(cr, uid, 'res.users', 'write', False) - if not canwrite: - if isinstance(ids, (int, long)): - result = override_password(result) - else: - result = map(override_password, result) - - return result + return super(res_users, self).read(cr, uid, ids, fields=fields, context=context, load=load) def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False, lazy=True): if uid != SUPERUSER_ID: From b2c20482c0c56453e86bb390d897169462f17168 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 1 Apr 2016 19:48:40 +0200 Subject: [PATCH 2/6] [FIX] res.partner: modifying users requires extra access --- openerp/addons/base/res/res_partner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 03d83364d49..b17bc442b65 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -524,6 +524,8 @@ class res_partner(osv.Model, format_address): result = super(res_partner, self).write(vals) for partner in self: + if any(u.has_group('base.group_user') for u in partner.user_ids): + self.env['res.users'].check_access_rights('write') self._fields_sync(partner, vals) return result From e2d0f1e974e938970196dc920b85f454b3d2fce1 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 1 Apr 2016 20:42:50 +0200 Subject: [PATCH 3/6] [FIX] base: require wizard for passwd change --- openerp/addons/base/res/res_users.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index 703462e3092..928a28414ef 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -285,7 +285,7 @@ class res_users(osv.osv): } # User can write on a few of his own fields (but not his groups for example) - SELF_WRITEABLE_FIELDS = ['password', 'signature', 'action_id', 'company_id', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz'] + SELF_WRITEABLE_FIELDS = ['signature', 'action_id', 'company_id', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz'] # User can read a few of his own fields SELF_READABLE_FIELDS = ['signature', 'company_id', 'login', 'email', 'name', 'image', 'image_medium', 'image_small', 'lang', 'tz', 'tz_offset', 'groups_id', 'partner_id', '__last_update', 'action_id'] @@ -521,7 +521,7 @@ class res_users(osv.osv): """ self.check(cr.dbname, uid, old_passwd) if new_passwd: - return self.write(cr, uid, uid, {'password': new_passwd}) + return self.write(cr, SUPERUSER_ID, uid, {'password': new_passwd}) raise UserError(_("Setting empty passwords is not allowed for security reasons!")) def preference_save(self, cr, uid, ids, context=None): From b7b26093288dfd09bc69bd6d476dc467cf9cc0b1 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 1 Apr 2016 20:44:10 +0200 Subject: [PATCH 4/6] [FIX] base_import: avoid JS error in callback --- addons/base_import/controllers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/base_import/controllers.py b/addons/base_import/controllers.py index 9a49384900f..84b645f149f 100644 --- a/addons/base_import/controllers.py +++ b/addons/base_import/controllers.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import cgi import json from openerp.http import Controller, route @@ -15,4 +16,4 @@ class ImportController(Controller): }, req.context) return 'window.top.%s(%s)' % ( - jsonp, json.dumps({'result': written})) + cgi.escape(jsonp), json.dumps({'result': written})) From f6090e69e648d5891cb887e82fbfcbc868b574ac Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 1 Apr 2016 20:44:52 +0200 Subject: [PATCH 5/6] [FIX] ir.attachment: verify mimetypes match w/ contents --- openerp/addons/base/ir/ir_attachment.py | 28 ++++++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index 2488726ff48..c94a3743830 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -12,7 +12,7 @@ from openerp import SUPERUSER_ID, tools from openerp.exceptions import AccessError from openerp.osv import fields,osv from openerp.tools.translate import _ -from openerp.tools.misc import ustr +from openerp.tools.misc import ustr, html_escape from openerp.tools.mimetypes import guess_mimetype _logger = logging.getLogger(__name__) @@ -199,14 +199,26 @@ class ir_attachment(osv.osv): :param values : dict of values to create or write an ir_attachment :return mime : string indicating the mimetype, or application/octet-stream by default """ - mimetype = 'application/octet-stream' - if values.get('datas_fname'): + mimetype = None + if values.get('mimetype'): + mimetype = values['mimetype'] + if not mimetype and values.get('datas_fname'): mimetype = mimetypes.guess_type(values['datas_fname'])[0] - if values.get('url'): + if not mimetype and values.get('url'): mimetype = mimetypes.guess_type(values['url'])[0] if values.get('datas') and (not mimetype or mimetype == 'application/octet-stream'): mimetype = guess_mimetype(values['datas'].decode('base64')) - return mimetype + return mimetype or 'application/octet-stream' + + def _check_contents(self, cr, uid, values, context=None): + mimetype = values['mimetype'] = self._compute_mimetype(values) + needs_escape = 'htm' in mimetype or '/ht' in mimetype # hta, html, xhtml, etc. + if needs_escape and not self.pool['res.users']._is_admin(cr, uid, [uid]): + if 'datas' in values: + values['datas'] = html_escape(values['datas'].decode('base64')).encode('base64') + else: + values['mimetype'] = 'text/plain' + return values def _index(self, cr, uid, bin_data, datas_fname, file_type): """ compute the index content of the given filename, or binary data. @@ -382,6 +394,8 @@ class ir_attachment(osv.osv): # remove computed field depending of datas for field in ['file_size', 'checksum']: vals.pop(field, False) + if 'mimetype' in vals or 'datas' in vals: + vals = self._check_contents(cr, uid, vals, context=context) return super(ir_attachment, self).write(cr, uid, ids, vals, context) def copy(self, cr, uid, id, default=None, context=None): @@ -409,9 +423,7 @@ class ir_attachment(osv.osv): # remove computed field depending of datas for field in ['file_size', 'checksum']: values.pop(field, False) - # if mimetype not given, compute it ! - if 'mimetype' not in values: - values['mimetype'] = self._compute_mimetype(values) + values = self._check_contents(cr, uid, values, context=context) self.check(cr, uid, [], mode='write', context=context, values=values) return super(ir_attachment, self).create(cr, uid, values, context) From 7a483a85d46a7298b0b3d27efeefbcd2a11772c4 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 29 Mar 2016 10:52:13 +0200 Subject: [PATCH 6/6] [FIX] *: do not use computed fields for resizing images Otherwise recomputation may fail with "record does not exist or has been deleted" when creating records with a value directly set for e.g. 'image_mediun' on a new product. The issue comes from the storage of the images. When creating a product, the creation of an attachment for storing the field `image_medium` triggers the recomputation of that field before its dependency `image` is set. As `image` is initially null, `image_medium` is recomputed as null, and this deletes the attachment just created before the latter has completed its creation! As a consequence, some code at the end of `create` for the attachment crashes because the record has been deleted. The following models have been fixed: `fleet.vehicle.model.brand`, `hr.employee`, `im_livechat.channel`, `mail.channel`, `payment.acquirer`, `pos.category`, `product.template`, `product.public.category`, `res.partner`. opw 666330 Closes #11516 --- addons/fleet/fleet.py | 26 ++++++--------- addons/hr/hr.py | 30 +++++++---------- .../im_livechat/models/im_livechat_channel.py | 22 +++++++------ addons/mail/models/mail_channel.py | 20 +++--------- addons/payment/models/payment_acquirer.py | 32 ++++++++----------- addons/point_of_sale/point_of_sale.py | 26 ++++++--------- addons/product/product.py | 22 +++---------- addons/website_sale/models/product.py | 26 ++++++--------- openerp/addons/base/res/res_partner.py | 22 +++---------- openerp/tools/image.py | 18 +++++++++++ 10 files changed, 97 insertions(+), 147 deletions(-) diff --git a/addons/fleet/fleet.py b/addons/fleet/fleet.py index 5108841e3e2..92b933df210 100644 --- a/addons/fleet/fleet.py +++ b/addons/fleet/fleet.py @@ -142,30 +142,24 @@ class fleet_vehicle_model_brand(osv.Model): image = openerp.fields.Binary("Logo", attachment=True, help="This field holds the image used as logo for the brand, limited to 1024x1024px.") - image_medium = openerp.fields.Binary("Medium-sized image", - compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, + image_medium = openerp.fields.Binary("Medium-sized image", attachment=True, help="Medium-sized logo of the brand. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") - image_small = openerp.fields.Binary("Small-sized image", - compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, + image_small = openerp.fields.Binary("Small-sized image", attachment=True, help="Small-sized logo of the brand. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") - @openerp.api.depends('image') - def _compute_images(self): - for rec in self: - rec.image_medium = tools.image_resize_image_medium(rec.image) - rec.image_small = tools.image_resize_image_small(rec.image) + @openerp.api.model + def create(self, vals): + tools.image_resize_images(vals) + return super(fleet_vehicle_model_brand, self).create(vals) - def _inverse_image_medium(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_medium) - - def _inverse_image_small(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_small) + @openerp.api.multi + def write(self, vals): + tools.image_resize_images(vals) + return super(fleet_vehicle_model_brand, self).write(vals) class fleet_vehicle(osv.Model): diff --git a/addons/hr/hr.py b/addons/hr/hr.py index 42dad3abcdd..5a429fc35d8 100644 --- a/addons/hr/hr.py +++ b/addons/hr/hr.py @@ -172,31 +172,15 @@ class hr_employee(osv.osv): # image: all image fields are base64 encoded and PIL-supported image = openerp.fields.Binary("Photo", attachment=True, help="This field holds the image used as photo for the employee, limited to 1024x1024px.") - image_medium = openerp.fields.Binary("Medium-sized photo", - compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, + image_medium = openerp.fields.Binary("Medium-sized photo", attachment=True, help="Medium-sized photo of the employee. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") - image_small = openerp.fields.Binary("Small-sized photo", - compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, + image_small = openerp.fields.Binary("Small-sized photo", attachment=True, help="Small-sized photo of the employee. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") - @api.depends('image') - def _compute_images(self): - for rec in self: - rec.image_medium = tools.image_resize_image_medium(rec.image) - rec.image_small = tools.image_resize_image_small(rec.image) - - def _inverse_image_medium(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_medium) - - def _inverse_image_small(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_small) - def _get_default_image(self, cr, uid, context=None): image_path = get_module_resource('hr', 'static/src/img', 'default_image.png') return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64')) @@ -207,6 +191,16 @@ class hr_employee(osv.osv): 'color': 0, } + @api.model + def create(self, vals): + tools.image_resize_images(vals) + return super(hr_employee, self).create(vals) + + @api.multi + def write(self, vals): + tools.image_resize_images(vals) + return super(hr_employee, self).write(vals) + def unlink(self, cr, uid, ids, context=None): resource_ids = [] for employee in self.browse(cr, uid, ids, context=context): diff --git a/addons/im_livechat/models/im_livechat_channel.py b/addons/im_livechat/models/im_livechat_channel.py index 061b94fcc69..731118d31c4 100644 --- a/addons/im_livechat/models/im_livechat_channel.py +++ b/addons/im_livechat/models/im_livechat_channel.py @@ -48,13 +48,11 @@ class ImLivechatChannel(models.Model): # images fields image = fields.Binary('Image', default=_default_image, attachment=True, help="This field holds the image used as photo for the group, limited to 1024x1024px.") - image_medium = fields.Binary('Medium', - compute='_compute_image', store=True, attachment=True, + image_medium = fields.Binary('Medium', attachment=True, help="Medium-sized photo of the group. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") - image_small = fields.Binary('Thumbnail', - compute='_compute_image', store=True, attachment=True, + image_small = fields.Binary('Thumbnail', attachment=True, help="Small-sized photo of the group. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") @@ -69,12 +67,6 @@ class ImLivechatChannel(models.Model): def _are_you_inside(self): self.are_you_inside = bool(self.env.uid in [u.id for u in self.user_ids]) - @api.one - @api.depends('image') - def _compute_image(self): - self.image_medium = tools.image_resize_image_medium(self.image) - self.image_small = tools.image_resize_image_small(self.image) - @api.multi def _compute_script_external(self): view = self.env['ir.model.data'].get_object('im_livechat', 'external_loader') @@ -111,6 +103,16 @@ class ImLivechatChannel(models.Model): else: record.rating_percentage_satisfaction = -1 + @api.model + def create(self, vals): + tools.image_resize_images(vals) + return super(ImLivechatChannel, self).create(vals) + + @api.multi + def write(self, vals): + tools.image_resize_images(vals) + return super(ImLivechatChannel, self).write(vals) + # -------------------------- # Action Methods # -------------------------- diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index 6f16888cccc..ffd63e9d794 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -72,13 +72,11 @@ class Channel(models.Model): # image: all image fields are base64 encoded and PIL-supported image = fields.Binary("Photo", default=_get_default_image, attachment=True, help="This field holds the image used as photo for the group, limited to 1024x1024px.") - image_medium = fields.Binary('Medium-sized photo', - compute='_get_image', inverse='_set_image_medium', store=True, attachment=True, + image_medium = fields.Binary('Medium-sized photo', attachment=True, help="Medium-sized photo of the group. It is automatically " "resized as a 128x128px image, with aspect ratio preserved. " "Use this field in form views or some kanban views.") - image_small = fields.Binary('Small-sized photo', - compute='_get_image', inverse='_set_image_small', store=True, attachment=True, + image_small = fields.Binary('Small-sized photo', attachment=True, help="Small-sized photo of the group. It is automatically " "resized as a 64x64px image, with aspect ratio preserved. " "Use this field anywhere a small image is required.") @@ -96,20 +94,9 @@ class Channel(models.Model): for record in self: record.is_member = record in membership_ids - @api.one - @api.depends('image') - def _get_image(self): - self.image_medium = tools.image_resize_image_medium(self.image) - self.image_small = tools.image_resize_image_small(self.image) - - def _set_image_medium(self): - self.image = tools.image_resize_image_big(self.image_medium) - - def _set_image_small(self): - self.image = tools.image_resize_image_big(self.image_small) - @api.model def create(self, vals): + tools.image_resize_images(vals) # Create channel and alias channel = super(Channel, self.with_context( alias_model_name=self._name, alias_parent_model_name=self._name, mail_create_nolog=True, mail_create_nosubscribe=True) @@ -143,6 +130,7 @@ class Channel(models.Model): @api.multi def write(self, vals): + tools.image_resize_images(vals) result = super(Channel, self).write(vals) if vals.get('group_ids'): self._subscribe_users() diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py index 803b3ce4700..bf83f8a4126 100644 --- a/addons/payment/models/payment_acquirer.py +++ b/addons/payment/models/payment_acquirer.py @@ -3,7 +3,7 @@ import logging import openerp from openerp.osv import osv, fields -from openerp.tools import float_round, float_repr, image_get_resized_images, image_resize_image_big +from openerp.tools import float_round, float_repr, image_resize_images from openerp.tools.translate import _ _logger = logging.getLogger(__name__) @@ -97,31 +97,15 @@ class PaymentAcquirer(osv.Model): image = openerp.fields.Binary("Image", attachment=True, help="This field holds the image used for this provider, limited to 1024x1024px") - image_medium = openerp.fields.Binary("Medium-sized image", - compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, + image_medium = openerp.fields.Binary("Medium-sized image", attachment=True, help="Medium-sized image of this provider. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") - image_small = openerp.fields.Binary("Small-sized image", - compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, + image_small = openerp.fields.Binary("Small-sized image", attachment=True, help="Small-sized image of this provider. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") - @openerp.api.depends('image') - def _compute_images(self): - for rec in self: - rec.image_medium = openerp.tools.image_resize_image_medium(rec.image) - rec.image_small = openerp.tools.image_resize_image_small(rec.image) - - def _inverse_image_medium(self): - for rec in self: - rec.image = openerp.tools.image_resize_image_big(rec.image_medium) - - def _inverse_image_small(self): - for rec in self: - rec.image = openerp.tools.image_resize_image_big(rec.image_small) - _defaults = { 'company_id': lambda self, cr, uid, obj, ctx=None: self.pool['res.users'].browse(cr, uid, uid).company_id.id, 'environment': 'prod', @@ -145,6 +129,16 @@ class PaymentAcquirer(osv.Model): (_check_required_if_provider, 'Required fields not filled', ['required for this provider']), ] + @openerp.api.model + def create(self, vals): + image_resize_images(vals) + return super(PaymentAcquirer, self).create(vals) + + @openerp.api.multi + def write(self, vals): + image_resize_images(vals) + return super(PaymentAcquirer, self).write(vals) + def get_form_action_url(self, cr, uid, id, context=None): """ Returns the form action URL, for form-based acquirer implementations. """ acquirer = self.browse(cr, uid, id, context=context) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 73ee1e53318..2115fdd5249 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -1525,30 +1525,24 @@ class pos_category(osv.osv): # In this case, the default image is set by the js code. image = openerp.fields.Binary("Image", attachment=True, help="This field holds the image used as image for the cateogry, limited to 1024x1024px.") - image_medium = openerp.fields.Binary("Medium-sized image", - compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, + image_medium = openerp.fields.Binary("Medium-sized image", attachment=True, help="Medium-sized image of the category. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") - image_small = openerp.fields.Binary("Small-sized image", - compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, + image_small = openerp.fields.Binary("Small-sized image", attachment=True, help="Small-sized image of the category. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") - @openerp.api.depends('image') - def _compute_images(self): - for rec in self: - rec.image_medium = tools.image_resize_image_medium(rec.image) - rec.image_small = tools.image_resize_image_small(rec.image) + @openerp.api.model + def create(self, vals): + tools.image_resize_images(vals) + return super(pos_category, self).create(vals) - def _inverse_image_medium(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_medium) - - def _inverse_image_small(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_small) + @openerp.api.multi + def write(self, vals): + tools.image_resize_images(vals) + return super(pos_category, self).write(vals) class product_template(osv.osv): _inherit = 'product.template' diff --git a/addons/product/product.py b/addons/product/product.py index 97dc6b778fe..50896fe34d2 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -537,31 +537,15 @@ class product_template(osv.osv): # image: all image fields are base64 encoded and PIL-supported image = openerp.fields.Binary("Image", attachment=True, help="This field holds the image used as image for the product, limited to 1024x1024px.") - image_medium = openerp.fields.Binary("Medium-sized image", - compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, + image_medium = openerp.fields.Binary("Medium-sized image", attachment=True, help="Medium-sized image of the product. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved, "\ "only when the image exceeds one of those sizes. Use this field in form views or some kanban views.") - image_small = openerp.fields.Binary("Small-sized image", - compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, + image_small = openerp.fields.Binary("Small-sized image", attachment=True, help="Small-sized image of the product. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") - @api.depends('image') - def _compute_images(self): - for rec in self: - rec.image_medium = tools.image_resize_image_medium(rec.image, avoid_if_small=True) - rec.image_small = tools.image_resize_image_small(rec.image) - - def _inverse_image_medium(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_medium) - - def _inverse_image_small(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_small) - def _price_get(self, cr, uid, products, ptype='list_price', context=None): if context is None: context = {} @@ -687,6 +671,7 @@ class product_template(osv.osv): def create(self, cr, uid, vals, context=None): ''' Store the initial standard price in order to be able to retrieve the cost of a product template for a given date''' + tools.image_resize_images(vals) product_template_id = super(product_template, self).create(cr, uid, vals, context=context) if not context or "create_product_product" not in context: self.create_variant_ids(cr, uid, [product_template_id], context=context) @@ -710,6 +695,7 @@ class product_template(osv.osv): return product_template_id def write(self, cr, uid, ids, vals, context=None): + tools.image_resize_images(vals) res = super(product_template, self).write(cr, uid, ids, vals, context=context) if 'attribute_line_ids' in vals or vals.get('active'): self.create_variant_ids(cr, uid, ids, context=context) diff --git a/addons/website_sale/models/product.py b/addons/website_sale/models/product.py index 201e4f48dd4..bf547b55ac8 100644 --- a/addons/website_sale/models/product.py +++ b/addons/website_sale/models/product.py @@ -60,30 +60,24 @@ class product_public_category(osv.osv): # In this case, the default image is set by the js code. image = openerp.fields.Binary("Image", attachment=True, help="This field holds the image used as image for the category, limited to 1024x1024px.") - image_medium = openerp.fields.Binary("Medium-sized image", - compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, + image_medium = openerp.fields.Binary("Medium-sized image", attachment=True, help="Medium-sized image of the category. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") - image_small = openerp.fields.Binary("Small-sized image", - compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, + image_small = openerp.fields.Binary("Small-sized image", attachment=True, help="Small-sized image of the category. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") - @openerp.api.depends('image') - def _compute_images(self): - for rec in self: - rec.image_medium = tools.image_resize_image_medium(rec.image) - rec.image_small = tools.image_resize_image_small(rec.image) + @openerp.api.model + def create(self, vals): + tools.image_resize_images(vals) + return super(product_public_category, self).create(vals) - def _inverse_image_medium(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_medium) - - def _inverse_image_small(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_small) + @openerp.api.multi + def write(self, vals): + tools.image_resize_images(vals) + return super(product_public_category, self).write(vals) class product_template(osv.Model): _inherit = ["product.template", "website.seo.metadata", 'website.published.mixin', 'rating.mixin'] diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index b17bc442b65..f369f234451 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -252,31 +252,15 @@ class res_partner(osv.Model, format_address): image = openerp.fields.Binary("Image", attachment=True, help="This field holds the image used as avatar for this contact, limited to 1024x1024px", default=lambda self: self._get_default_image(False, True)) - image_medium = openerp.fields.Binary("Medium-sized image", - compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True, + image_medium = openerp.fields.Binary("Medium-sized image", attachment=True, help="Medium-sized image of this contact. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") - image_small = openerp.fields.Binary("Small-sized image", - compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True, + image_small = openerp.fields.Binary("Small-sized image", attachment=True, help="Small-sized image of this contact. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") - @api.depends('image') - def _compute_images(self): - for rec in self: - rec.image_medium = tools.image_resize_image_medium(rec.image) - rec.image_small = tools.image_resize_image_small(rec.image) - - def _inverse_image_medium(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_medium) - - def _inverse_image_small(self): - for rec in self: - rec.image = tools.image_resize_image_big(rec.image_small) - @api.model def _default_category(self): category_id = self.env.context.get('category_id', False) @@ -521,6 +505,7 @@ class res_partner(osv.Model, format_address): vals['is_company'] = c_type == 'company' elif 'is_company' in vals: vals['company_type'] = is_company and 'company' or 'person' + tools.image_resize_images(vals) result = super(res_partner, self).write(vals) for partner in self: @@ -544,6 +529,7 @@ class res_partner(osv.Model, format_address): vals['is_company'] = c_type == 'company' else: vals['company_type'] = is_company and 'company' or 'person' + tools.image_resize_images(vals) partner = super(res_partner, self).create(vals) self._fields_sync(partner, vals) self._handle_first_contact_creation(partner) diff --git a/openerp/tools/image.py b/openerp/tools/image.py index c6d0ad9aa43..f17623cbe6e 100644 --- a/openerp/tools/image.py +++ b/openerp/tools/image.py @@ -256,6 +256,24 @@ def image_get_resized_images(base64_source, return_big=False, return_medium=True return_dict[small_name] = image_resize_image_small(base64_source, avoid_if_small=avoid_resize_small) return return_dict +def image_resize_images(vals, big_name='image', medium_name='image_medium', small_name='image_small'): + """ Update ``vals`` with image fields resized as expected. """ + if big_name in vals: + vals.update(image_get_resized_images(vals[big_name], + return_big=True, return_medium=True, return_small=True, + big_name=big_name, medium_name=medium_name, small_name=small_name, + avoid_resize_big=True, avoid_resize_medium=False, avoid_resize_small=False)) + elif medium_name in vals: + vals.update(image_get_resized_images(vals[medium_name], + return_big=True, return_medium=True, return_small=True, + big_name=big_name, medium_name=medium_name, small_name=small_name, + avoid_resize_big=True, avoid_resize_medium=True, avoid_resize_small=False)) + elif small_name in vals: + vals.update(image_get_resized_images(vals[small_name], + return_big=True, return_medium=True, return_small=True, + big_name=big_name, medium_name=medium_name, small_name=small_name, + avoid_resize_big=True, avoid_resize_medium=True, avoid_resize_small=True)) + if __name__=="__main__": import sys