[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
This commit is contained in:
Raphael Collet
2016-04-02 00:47:57 +02:00
committed by Olivier Dony
parent f6090e69e6
commit 7a483a85d4
10 changed files with 97 additions and 147 deletions
+10 -16
View File
@@ -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):
+12 -18
View File
@@ -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):
@@ -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
# --------------------------
+4 -16
View File
@@ -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()
+13 -19
View File
@@ -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)
+10 -16
View File
@@ -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'
+4 -18
View File
@@ -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)
+10 -16
View File
@@ -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']
+4 -18
View File
@@ -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)
+18
View File
@@ -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