From f6c4e9e235b4d08064421d51e7bd4c39260780c3 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Fri, 25 Apr 2014 22:10:40 +0200 Subject: [PATCH 01/25] [ADD] start the module binary field in order to store correctly the binary in OpenERP. My train arrive so I will continue latter --- binary_field/__init__.py | 24 +++++++ binary_field/__openerp__.py | 50 ++++++++++++++ binary_field/binary.py | 128 ++++++++++++++++++++++++++++++++++++ binary_field/data.xml | 9 +++ binary_field/fields.py | 100 ++++++++++++++++++++++++++++ 5 files changed, 311 insertions(+) create mode 100644 binary_field/__init__.py create mode 100644 binary_field/__openerp__.py create mode 100644 binary_field/binary.py create mode 100644 binary_field/data.xml create mode 100644 binary_field/fields.py diff --git a/binary_field/__init__.py b/binary_field/__init__.py new file mode 100644 index 00000000000..6ed503f6b73 --- /dev/null +++ b/binary_field/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2013 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################### + +from . import binary +from . import fields diff --git a/binary_field/__openerp__.py b/binary_field/__openerp__.py new file mode 100644 index 00000000000..c24e4f0224f --- /dev/null +++ b/binary_field/__openerp__.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2013 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################### + +{'name': 'Binary Field', + 'version': '0.0.1', + 'author': 'Akretion', + 'website': 'www.akretion.com', + 'license': 'AGPL-3', + 'category': 'Framework', + 'description': """This module extend the fields class in order to add 3 new +type of fields +- BinaryStore +- ImageStore +- ImageRezise + +Now let's do it ;) + + """, + 'depends': [ + 'base', + ], + 'data': [ + 'data.xml', + ], + 'installable': True, + 'application': True, +} + + + + diff --git a/binary_field/binary.py b/binary_field/binary.py new file mode 100644 index 00000000000..90d6211fdc1 --- /dev/null +++ b/binary_field/binary.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# Copyright (C) 2014 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################### + +from openerp.osv import fields, orm +from openerp.tools.translate import _ +from openerp.addons.base.ir import ir_attachment +import os +import logging + +_logger = logging.getLogger(__name__) + + +class BinaryBinary(orm.Model): + _name = 'binary.binary' + _description = 'binary.binary' + + _columns = { + #'name': fields.char('Name',size=256, required=True), + 'store_fname': fields.char('Stored Filename', size=256), + #'extension': fields.char('Extension', size=4), + 'file_size': fields.integer('File Size'), + } + + def _get_location(self, cr, uid): + base_location = self.pool.get('ir.config_parameter').\ + get_param(cr, uid, 'binary.location') + if not base_location: + raise orm.except_orm( + _('Configuration Error'), + _('The "binary.location" is empty, please fill it in' + 'Configuration > Parameters > System Parameters')) + return base_location + + def add(self, cr, uid, value, field_key, context=None): + if not value: + return None + base_location = self._get_location(cr, uid) + # Hack for passing the field_key in the full path + location = (base_location, field_key) + file_size = len(value.decode('base64')) + fname = self._file_write(cr, uid, location, value) + return self.create(cr, uid, { + 'store_fname': fname, + 'file_size': file_size, + }, context=context) + + def update(self, cr, uid, binary_id, value, field_key, context=None): + binary = self.browse(cr, uid, binary_id, context=context) + base_location = self._get_location(cr, uid) + # Hack for passing the field_key in the full path + location = (base_location, field_key) + self._file_delete(cr, uid, location, binary.store_fname) + if not value: + return None + fname = self._file_write(cr, uid, location, value) + file_size = len(value.decode('base64')) + self.write(cr, uid, binary_id, { + 'store_fname': fname, + 'file_size': file_size, + }, context=context) + return True + + def get_content(self, cr, uid, field_key, binary_id, context=None): + binary = self.browse(cr, uid, binary_id, context=context) + base_location = self._get_location(cr, uid) + # Hack for passing the field_key in the full path + location = (base_location, field_key) + return self._file_read(cr, uid, base_location, binary.store_fname) + + def _file_read(self, cr, uid, location, fname): + return self.pool['ir.attachment']._file_read(cr, uid, location, fname) + + def _file_write(self, cr, uid, location, value): + return self.pool['ir.attachment']._file_write(cr, uid, location, value) + + def _file_delete(self, cr, uid, location, fname): + count = self.search(cr, 1, [('store_fname', '=', fname)], count=True) + if count <= 1: + full_path = self._full_path(cr, uid, location, fname) + try: + os.unlink(full_path) + except OSError: + _logger.error("_file_delete could not unlink %s", full_path) + except IOError: + # Harmless and needed for race conditions + _logger.error("_file_delete could not unlink %s", full_path) + + +class IrAttachment(orm.Model): + _inherit = 'ir.attachment' + + def _full_path(self, cr, uid, location, path): + #TODO add the posibility to customise the field_key + #maybe we can add a the field key in the ir.config.parameter + #and then retrieve an new path base on the config? + + # Hack for passing the field_key in the full path + if isinstance(location, tuple): + base_location, field_key = location + path = os.path.join(field_key, path) + else: + base_location = location + return super(IrAttachment, self).\ + _full_path(cr, uid, base_location, path) + + diff --git a/binary_field/data.xml b/binary_field/data.xml new file mode 100644 index 00000000000..2f827b6fd39 --- /dev/null +++ b/binary_field/data.xml @@ -0,0 +1,9 @@ + + + + + binary.location + file:///filestore + + + diff --git a/binary_field/fields.py b/binary_field/fields.py new file mode 100644 index 00000000000..63007963ffe --- /dev/null +++ b/binary_field/fields.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2014 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################### + +from openerp.osv import fields, orm + +class BinaryField(fields.function): + + def __init__(self, string, filters=None, **kwargs): + self.filters = filters + super(BinaryField, self).__init__( + string=string, + fnct=self._fnct_read, + fnct_inv=self._fnct_write, + type='binary', + multi=False, + **kwargs) + + def _get_binary_id(self, cr, uid, obj, field_name, record_id, context=None): + cr.execute( + "SELECT " + field_name +"_info_id FROM " + obj._table + " WHERE id = %s", + (record_id,)) + return cr.fetchone()[0] + + def _get_binary(self, cr, uid, obj, field_name, record_id, context=None): + binary_obj = obj.pool['binary.binary'] + binary_id = self._get_binary_id( + cr, uid, obj, field_name, record_id, context=context) + if not binary_id: + return None + field_key = "%s-%s" % (obj._name, field_name) + return binary_obj.get_content(cr, uid, field_key, binary_id, context=context) + + def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, + context=None): + binary_obj = obj.pool['binary.binary'] + if type(ids) in (list, tuple): + assert len(ids) == 1, 'multi mode is not supported' + record_id = ids[0] + else: + record_id = ids + field_key = "%s-%s" % (obj._name, field_name) + binary_id = self._get_binary_id( + cr, uid, obj, field_name, record_id, context=context) + if binary_id: + res_id = binary_obj.update( + cr, uid, binary_id, value, field_key, context=context) + else: + res_id = binary_obj.add(cr, uid, value, field_key, context=context) + obj.write(cr, uid, record_id, {'%s_info_id' % field_name: res_id}, context=context) + return True + + def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): + result = {} + for record_id in ids: + result[record_id] = self._get_binary(cr, uid, obj, field_name, + record_id, context=context) + +#TODO do not forget to add this for compatibility with binary field +# if val and context.get('bin_size_%s' % name, context.get('bin_size')): +# res[i] = tools.human_size(long(val)) +# else: +# res[i] = val + return result + +fields.BinaryField = BinaryField + + +original__init__ = orm.BaseModel.__init__ + +def __init__(self, pool, cr): + original__init__(self, pool, cr) + if self.pool['binary.binary']: + print 'I should update something here' + additionnal_field = {} + for field in self._columns: + if isinstance(self._columns[field], BinaryField): + additionnal_field['%s_info_id'%field] = \ + fields.many2one('binary.binary', 'Binary') + self._columns.update(additionnal_field) + +orm.BaseModel.__init__ = __init__ From 6bf995b7ffc95226e3c4fe9d2ed709b4f6fff681 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Mon, 28 Apr 2014 08:28:24 +0200 Subject: [PATCH 02/25] [FIX] finish my previous work, now binaryField are working, let's do the image field --- binary_field/binary.py | 15 ++------------- binary_field/fields.py | 2 +- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/binary_field/binary.py b/binary_field/binary.py index 90d6211fdc1..6bce482712d 100644 --- a/binary_field/binary.py +++ b/binary_field/binary.py @@ -87,7 +87,7 @@ def get_content(self, cr, uid, field_key, binary_id, context=None): base_location = self._get_location(cr, uid) # Hack for passing the field_key in the full path location = (base_location, field_key) - return self._file_read(cr, uid, base_location, binary.store_fname) + return self._file_read(cr, uid, location, binary.store_fname) def _file_read(self, cr, uid, location, fname): return self.pool['ir.attachment']._file_read(cr, uid, location, fname) @@ -96,16 +96,7 @@ def _file_write(self, cr, uid, location, value): return self.pool['ir.attachment']._file_write(cr, uid, location, value) def _file_delete(self, cr, uid, location, fname): - count = self.search(cr, 1, [('store_fname', '=', fname)], count=True) - if count <= 1: - full_path = self._full_path(cr, uid, location, fname) - try: - os.unlink(full_path) - except OSError: - _logger.error("_file_delete could not unlink %s", full_path) - except IOError: - # Harmless and needed for race conditions - _logger.error("_file_delete could not unlink %s", full_path) + return self.pool['ir.attachment']._file_delete(cr, uid, location, fname) class IrAttachment(orm.Model): @@ -124,5 +115,3 @@ def _full_path(self, cr, uid, location, path): base_location = location return super(IrAttachment, self).\ _full_path(cr, uid, base_location, path) - - diff --git a/binary_field/fields.py b/binary_field/fields.py index 63007963ffe..78ebb8a07e9 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -65,7 +65,7 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, cr, uid, binary_id, value, field_key, context=context) else: res_id = binary_obj.add(cr, uid, value, field_key, context=context) - obj.write(cr, uid, record_id, {'%s_info_id' % field_name: res_id}, context=context) + obj.write(cr, uid, record_id, {'%s_info_id' % field_name: res_id}, context=context) return True def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): From 2a4d260bfd6e33884a7cb3fd1e7056ac37d6ed06 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Mon, 28 Apr 2014 08:37:28 +0200 Subject: [PATCH 03/25] [IMP] add support of Image --- binary_field/fields.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/binary_field/fields.py b/binary_field/fields.py index 78ebb8a07e9..adb07528e97 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -81,7 +81,21 @@ def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): # res[i] = val return result +class ImageField(BinaryField): + + def __init__(self, string, filters=None, **kwargs): + self.filters = filters + super(BinaryField, self).__init__( + string=string, + fnct=self._fnct_read, + fnct_inv=self._fnct_write, + type='image', + multi=False, + **kwargs) + + fields.BinaryField = BinaryField +fields.ImageField = ImageField original__init__ = orm.BaseModel.__init__ @@ -93,6 +107,7 @@ def __init__(self, pool, cr): additionnal_field = {} for field in self._columns: if isinstance(self._columns[field], BinaryField): + print 'add field', '%s_info_id'%field additionnal_field['%s_info_id'%field] = \ fields.many2one('binary.binary', 'Binary') self._columns.update(additionnal_field) From a3ccee01cd53f1fe87d0498c703efdc12bd1c5e4 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Mon, 28 Apr 2014 19:11:39 +0200 Subject: [PATCH 04/25] [IMP] first version of working resize image --- binary_field/fields.py | 62 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/binary_field/fields.py b/binary_field/fields.py index adb07528e97..2c9be88338e 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -21,16 +21,19 @@ ############################################################################### from openerp.osv import fields, orm +from openerp.tools import image_resize_image + class BinaryField(fields.function): def __init__(self, string, filters=None, **kwargs): + if not kwargs.get('type'): + kwargs['type'] = 'binary' self.filters = filters super(BinaryField, self).__init__( string=string, fnct=self._fnct_read, fnct_inv=self._fnct_write, - type='binary', multi=False, **kwargs) @@ -85,31 +88,72 @@ class ImageField(BinaryField): def __init__(self, string, filters=None, **kwargs): self.filters = filters - super(BinaryField, self).__init__( + super(ImageField, self).__init__( string=string, - fnct=self._fnct_read, - fnct_inv=self._fnct_write, - type='image', - multi=False, **kwargs) +class ImageResizeField(ImageField): + + def __init__(self, string, related_field, height, width, compute='on_write', filters=None, **kwargs): + self.filters = filters + self.height = height + self.width = width + self.compute = compute #on_read/on_write + self.related_field = related_field + super(ImageResizeField, self).__init__( + string=string, + **kwargs) + + def _get_binary(self, cr, uid, obj, field_name, record_id, context=None): + #Idée via le fonction store, je trigger le write + #et la en fonciton du mode soit simplement je vire les images + #soit je les vire et je les regénère + if context.get('bin_size'): + return 2 + resize_field = obj._columns[field_name] + record = obj.browse(cr, uid, record_id, context=context) + original_image = record[resize_field.related_field] + size = (resize_field.height, resize_field.width) + return image_resize_image(original_image, size) + + +# binary_obj = obj.pool['binary.binary'] +# binary_id = self._get_binary_id( +# cr, uid, obj, field_name, record_id, context=context) +# if not binary_id: +# return None +# field_key = "%s-%s" % (obj._name, field_name) +# return binary_obj.get_content(cr, uid, field_key, binary_id, context=context) +# + + + fields.BinaryField = BinaryField fields.ImageField = ImageField +fields.ImageResizeField = ImageResizeField original__init__ = orm.BaseModel.__init__ def __init__(self, pool, cr): original__init__(self, pool, cr) - if self.pool['binary.binary']: - print 'I should update something here' + if self.pool.get('binary.binary'): additionnal_field = {} for field in self._columns: if isinstance(self._columns[field], BinaryField): - print 'add field', '%s_info_id'%field additionnal_field['%s_info_id'%field] = \ fields.many2one('binary.binary', 'Binary') + + #Inject the store invalidation function for ImageResize + if isinstance(self._columns[field], ImageResizeField): + self._columns[field].store = { + self._name: ( + lambda self, cr, uid, ids, c={}: ids, + [self._columns[field].related_field], + 10), + } + self._columns.update(additionnal_field) orm.BaseModel.__init__ = __init__ From 6d363ca6847657e453eb988c9815cf9ee575736b Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Tue, 29 Apr 2014 16:56:34 +0200 Subject: [PATCH 05/25] [IMP] add cache for resized image --- binary_field/fields.py | 102 ++++++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 37 deletions(-) diff --git a/binary_field/fields.py b/binary_field/fields.py index 2c9be88338e..ea9c847de3f 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -22,20 +22,23 @@ from openerp.osv import fields, orm from openerp.tools import image_resize_image +import logging +_logger = logging.getLogger(__name__) class BinaryField(fields.function): def __init__(self, string, filters=None, **kwargs): - if not kwargs.get('type'): - kwargs['type'] = 'binary' + new_kwargs = { + 'type': 'binary', + 'string': string, + 'fnct': self._fnct_read, + 'fnct_inv': self._fnct_write, + 'multi': False, + } + new_kwargs.update(kwargs) self.filters = filters - super(BinaryField, self).__init__( - string=string, - fnct=self._fnct_read, - fnct_inv=self._fnct_write, - multi=False, - **kwargs) + super(BinaryField, self).__init__(**new_kwargs) def _get_binary_id(self, cr, uid, obj, field_name, record_id, context=None): cr.execute( @@ -54,21 +57,21 @@ def _get_binary(self, cr, uid, obj, field_name, record_id, context=None): def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, context=None): + if not isinstance(ids, (list, tuple)): + ids = [ids] binary_obj = obj.pool['binary.binary'] - if type(ids) in (list, tuple): - assert len(ids) == 1, 'multi mode is not supported' - record_id = ids[0] - else: - record_id = ids - field_key = "%s-%s" % (obj._name, field_name) - binary_id = self._get_binary_id( - cr, uid, obj, field_name, record_id, context=context) - if binary_id: - res_id = binary_obj.update( - cr, uid, binary_id, value, field_key, context=context) - else: - res_id = binary_obj.add(cr, uid, value, field_key, context=context) - obj.write(cr, uid, record_id, {'%s_info_id' % field_name: res_id}, context=context) + for record_id in ids: + field_key = "%s-%s" % (obj._name, field_name) + binary_id = self._get_binary_id( + cr, uid, obj, field_name, record_id, context=context) + if binary_id: + res_id = binary_obj.update( + cr, uid, binary_id, value, field_key, context=context) + else: + res_id = binary_obj.add(cr, uid, value, field_key, context=context) + obj.write(cr, uid, record_id, { + '%s_info_id' % field_name: res_id, + }, context=context) return True def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): @@ -92,6 +95,18 @@ def __init__(self, string, filters=None, **kwargs): string=string, **kwargs) + def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, + context=None): + super(ImageField, self)._fnct_write( + obj, cr, uid, ids, field_name, value, args, context=context) + for name, field in obj._columns.items(): + if isinstance(field, ImageResizeField) \ + and field.related_field == field_name: + field._refresh_cache( + obj, cr, uid, ids, name, context=context) + return True + + class ImageResizeField(ImageField): def __init__(self, string, related_field, height, width, compute='on_write', filters=None, **kwargs): @@ -110,23 +125,36 @@ def _get_binary(self, cr, uid, obj, field_name, record_id, context=None): #soit je les vire et je les regénère if context.get('bin_size'): return 2 - resize_field = obj._columns[field_name] - record = obj.browse(cr, uid, record_id, context=context) - original_image = record[resize_field.related_field] - size = (resize_field.height, resize_field.width) - return image_resize_image(original_image, size) - - -# binary_obj = obj.pool['binary.binary'] -# binary_id = self._get_binary_id( -# cr, uid, obj, field_name, record_id, context=context) -# if not binary_id: -# return None -# field_key = "%s-%s" % (obj._name, field_name) -# return binary_obj.get_content(cr, uid, field_key, binary_id, context=context) -# + return super(ImageResizeField, self)._get_binary( + cr, uid, obj, field_name, record_id, context=context) + def _refresh_cache(self, obj, cr, uid, ids, field_name, context=None): + if not isinstance(ids, (list, tuple)): + ids = [ids] + for record_id in ids: + _logger.debug('Refresh Image Cache from the field %s of object %s ' + 'id : %s' % (field_name, obj._name, record_id)) + field = obj._columns[field_name] + record = obj.browse(cr, uid, record_id, context=context) + original_image = record[field.related_field] + if original_image: + size = (field.height, field.width) + resized_image = image_resize_image(original_image, size) + else: + resized_image = None + ctx = context.copy() + ctx['refresh_image_cache'] = True + self._fnct_write(obj, cr, uid, [record_id], field_name, + resized_image, None, context=ctx) + def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, + context=None): + if context is not None and context.get('refresh_image_cache'): + field = field_name + else: + field = obj._columns[field_name].related_field + return super(ImageResizeField, self)._fnct_write( + obj, cr, uid, ids, field, value, args, context=context) fields.BinaryField = BinaryField From ffd4d87ed92c44d87e44288361e46e9c6e455253 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Thu, 1 May 2014 11:04:54 +0200 Subject: [PATCH 06/25] [CLEAN] pep-8 clean up and remove dead code --- binary_field/binary.py | 5 ++--- binary_field/fields.py | 43 +++++++++++++++++++++++------------------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/binary_field/binary.py b/binary_field/binary.py index 6bce482712d..55c5bd3dad1 100644 --- a/binary_field/binary.py +++ b/binary_field/binary.py @@ -25,7 +25,6 @@ from openerp.osv import fields, orm from openerp.tools.translate import _ -from openerp.addons.base.ir import ir_attachment import os import logging @@ -81,7 +80,7 @@ def update(self, cr, uid, binary_id, value, field_key, context=None): 'file_size': file_size, }, context=context) return True - + def get_content(self, cr, uid, field_key, binary_id, context=None): binary = self.browse(cr, uid, binary_id, context=context) base_location = self._get_location(cr, uid) @@ -106,7 +105,7 @@ def _full_path(self, cr, uid, location, path): #TODO add the posibility to customise the field_key #maybe we can add a the field key in the ir.config.parameter #and then retrieve an new path base on the config? - + # Hack for passing the field_key in the full path if isinstance(location, tuple): base_location, field_key = location diff --git a/binary_field/fields.py b/binary_field/fields.py index ea9c847de3f..dc0beac2e35 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- ############################################################################### # -# Module for OpenERP +# Module for OpenERP # Copyright (C) 2014 Akretion (http://www.akretion.com). # @author Sébastien BEAU # @@ -34,7 +34,7 @@ def __init__(self, string, filters=None, **kwargs): 'string': string, 'fnct': self._fnct_read, 'fnct_inv': self._fnct_write, - 'multi': False, + 'multi': False, } new_kwargs.update(kwargs) self.filters = filters @@ -42,7 +42,8 @@ def __init__(self, string, filters=None, **kwargs): def _get_binary_id(self, cr, uid, obj, field_name, record_id, context=None): cr.execute( - "SELECT " + field_name +"_info_id FROM " + obj._table + " WHERE id = %s", + "SELECT " + field_name + "_info_id FROM " + + obj._table + " WHERE id = %s", (record_id,)) return cr.fetchone()[0] @@ -53,7 +54,8 @@ def _get_binary(self, cr, uid, obj, field_name, record_id, context=None): if not binary_id: return None field_key = "%s-%s" % (obj._name, field_name) - return binary_obj.get_content(cr, uid, field_key, binary_id, context=context) + return binary_obj.get_content( + cr, uid, field_key, binary_id, context=context) def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, context=None): @@ -68,7 +70,8 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, res_id = binary_obj.update( cr, uid, binary_id, value, field_key, context=context) else: - res_id = binary_obj.add(cr, uid, value, field_key, context=context) + res_id = binary_obj.add( + cr, uid, value, field_key, context=context) obj.write(cr, uid, record_id, { '%s_info_id' % field_name: res_id, }, context=context) @@ -77,8 +80,8 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): result = {} for record_id in ids: - result[record_id] = self._get_binary(cr, uid, obj, field_name, - record_id, context=context) + result[record_id] = self._get_binary( + cr, uid, obj, field_name, record_id, context=context) #TODO do not forget to add this for compatibility with binary field # if val and context.get('bin_size_%s' % name, context.get('bin_size')): @@ -87,6 +90,7 @@ def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): # res[i] = val return result + class ImageField(BinaryField): def __init__(self, string, filters=None, **kwargs): @@ -101,24 +105,24 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, obj, cr, uid, ids, field_name, value, args, context=context) for name, field in obj._columns.items(): if isinstance(field, ImageResizeField) \ - and field.related_field == field_name: - field._refresh_cache( - obj, cr, uid, ids, name, context=context) + and field.related_field == field_name: + field._refresh_cache( + obj, cr, uid, ids, name, context=context) return True class ImageResizeField(ImageField): - def __init__(self, string, related_field, height, width, compute='on_write', filters=None, **kwargs): + def __init__(self, string, related_field, height, width, + filters=None, **kwargs): self.filters = filters self.height = height self.width = width - self.compute = compute #on_read/on_write self.related_field = related_field super(ImageResizeField, self).__init__( string=string, **kwargs) - + def _get_binary(self, cr, uid, obj, field_name, record_id, context=None): #Idée via le fonction store, je trigger le write #et la en fonciton du mode soit simplement je vire les images @@ -126,14 +130,14 @@ def _get_binary(self, cr, uid, obj, field_name, record_id, context=None): if context.get('bin_size'): return 2 return super(ImageResizeField, self)._get_binary( - cr, uid, obj, field_name, record_id, context=context) + cr, uid, obj, field_name, record_id, context=context) def _refresh_cache(self, obj, cr, uid, ids, field_name, context=None): if not isinstance(ids, (list, tuple)): ids = [ids] for record_id in ids: _logger.debug('Refresh Image Cache from the field %s of object %s ' - 'id : %s' % (field_name, obj._name, record_id)) + 'id : %s' % (field_name, obj._name, record_id)) field = obj._columns[field_name] record = obj.browse(cr, uid, record_id, context=context) original_image = record[field.related_field] @@ -145,7 +149,7 @@ def _refresh_cache(self, obj, cr, uid, ids, field_name, context=None): ctx = context.copy() ctx['refresh_image_cache'] = True self._fnct_write(obj, cr, uid, [record_id], field_name, - resized_image, None, context=ctx) + resized_image, None, context=ctx) def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, context=None): @@ -154,7 +158,7 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, else: field = obj._columns[field_name].related_field return super(ImageResizeField, self)._fnct_write( - obj, cr, uid, ids, field, value, args, context=context) + obj, cr, uid, ids, field, value, args, context=context) fields.BinaryField = BinaryField @@ -164,13 +168,14 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, original__init__ = orm.BaseModel.__init__ + def __init__(self, pool, cr): original__init__(self, pool, cr) if self.pool.get('binary.binary'): additionnal_field = {} for field in self._columns: if isinstance(self._columns[field], BinaryField): - additionnal_field['%s_info_id'%field] = \ + additionnal_field['%s_info_id' % field] = \ fields.many2one('binary.binary', 'Binary') #Inject the store invalidation function for ImageResize @@ -181,7 +186,7 @@ def __init__(self, pool, cr): [self._columns[field].related_field], 10), } - self._columns.update(additionnal_field) + orm.BaseModel.__init__ = __init__ From fbc360afef99f1f376d5468e6ea7e6b1464a5891 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Thu, 1 May 2014 11:50:14 +0200 Subject: [PATCH 07/25] [ADD] add an example --- binary_field_example/__init__.py | 24 +++++++++++++ binary_field_example/__openerp__.py | 44 +++++++++++++++++++++++ binary_field_example/res_company.py | 38 ++++++++++++++++++++ binary_field_example/res_company_view.xml | 18 ++++++++++ 4 files changed, 124 insertions(+) create mode 100644 binary_field_example/__init__.py create mode 100644 binary_field_example/__openerp__.py create mode 100644 binary_field_example/res_company.py create mode 100644 binary_field_example/res_company_view.xml diff --git a/binary_field_example/__init__.py b/binary_field_example/__init__.py new file mode 100644 index 00000000000..2000b2edc41 --- /dev/null +++ b/binary_field_example/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2013 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################### + +from . import res_company + diff --git a/binary_field_example/__openerp__.py b/binary_field_example/__openerp__.py new file mode 100644 index 00000000000..62aee41140f --- /dev/null +++ b/binary_field_example/__openerp__.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2013 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################### + +{'name': 'binary field example', + 'version': '0.0.1', + 'author': 'Akretion', + 'website': 'www.akretion.com', + 'license': 'AGPL-3', + 'category': 'Generic Modules', + 'description': """Just an example + + """, + 'depends': [ + 'binary_field', + ], + 'data': [ + 'res_company_view.xml', + ], + 'installable': True, + 'application': True, +} + + + + diff --git a/binary_field_example/res_company.py b/binary_field_example/res_company.py new file mode 100644 index 00000000000..56414c15b02 --- /dev/null +++ b/binary_field_example/res_company.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2013 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################### + +from openerp.osv import fields, orm + + +class ResCompany(orm.Model): + _inherit = 'res.company' + + _columns = { + 'binary_test': fields.BinaryField('Test Binary'), + 'image_test': fields.ImageField('Test Image'), + 'image_test_resize': fields.ImageResizeField( + related_field='image_test', + string='Test Image small', + height=64, + width=64, + ), + } diff --git a/binary_field_example/res_company_view.xml b/binary_field_example/res_company_view.xml new file mode 100644 index 00000000000..9821a1249a8 --- /dev/null +++ b/binary_field_example/res_company_view.xml @@ -0,0 +1,18 @@ + + + + + + res.company + + + + + + + + + + + + From e555dd0c5b5ec1597678d417cd47c2194ef2e6a2 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Thu, 1 May 2014 20:44:29 +0200 Subject: [PATCH 08/25] [REf] extract the way to build the key field --- binary_field/fields.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/binary_field/fields.py b/binary_field/fields.py index dc0beac2e35..8f97c9b9dd0 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -46,6 +46,9 @@ def _get_binary_id(self, cr, uid, obj, field_name, record_id, context=None): + obj._table + " WHERE id = %s", (record_id,)) return cr.fetchone()[0] + + def _build_field_key(self, obj, field_name): + return "%s-%s" % (obj._name, field_name) def _get_binary(self, cr, uid, obj, field_name, record_id, context=None): binary_obj = obj.pool['binary.binary'] @@ -53,7 +56,7 @@ def _get_binary(self, cr, uid, obj, field_name, record_id, context=None): cr, uid, obj, field_name, record_id, context=context) if not binary_id: return None - field_key = "%s-%s" % (obj._name, field_name) + field_key = self._build_field_key(obj, field_name) return binary_obj.get_content( cr, uid, field_key, binary_id, context=context) @@ -63,7 +66,7 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, ids = [ids] binary_obj = obj.pool['binary.binary'] for record_id in ids: - field_key = "%s-%s" % (obj._name, field_name) + field_key = self._build_field_key(obj, field_name) binary_id = self._get_binary_id( cr, uid, obj, field_name, record_id, context=context) if binary_id: From c2c468a87f81f5ec23c77f4575e0876ec8849492 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Thu, 1 May 2014 20:55:51 +0200 Subject: [PATCH 09/25] [REF] prepare a big code refactor. First step moving all the code in the same file --- binary_field/binary.py | 116 ----------------------------------------- binary_field/fields.py | 92 +++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 118 deletions(-) delete mode 100644 binary_field/binary.py diff --git a/binary_field/binary.py b/binary_field/binary.py deleted file mode 100644 index 55c5bd3dad1..00000000000 --- a/binary_field/binary.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################### -# -# Module for OpenERP -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2009 Tiny SPRL (). -# -# Copyright (C) 2014 Akretion (http://www.akretion.com). -# @author Sébastien BEAU -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################### - -from openerp.osv import fields, orm -from openerp.tools.translate import _ -import os -import logging - -_logger = logging.getLogger(__name__) - - -class BinaryBinary(orm.Model): - _name = 'binary.binary' - _description = 'binary.binary' - - _columns = { - #'name': fields.char('Name',size=256, required=True), - 'store_fname': fields.char('Stored Filename', size=256), - #'extension': fields.char('Extension', size=4), - 'file_size': fields.integer('File Size'), - } - - def _get_location(self, cr, uid): - base_location = self.pool.get('ir.config_parameter').\ - get_param(cr, uid, 'binary.location') - if not base_location: - raise orm.except_orm( - _('Configuration Error'), - _('The "binary.location" is empty, please fill it in' - 'Configuration > Parameters > System Parameters')) - return base_location - - def add(self, cr, uid, value, field_key, context=None): - if not value: - return None - base_location = self._get_location(cr, uid) - # Hack for passing the field_key in the full path - location = (base_location, field_key) - file_size = len(value.decode('base64')) - fname = self._file_write(cr, uid, location, value) - return self.create(cr, uid, { - 'store_fname': fname, - 'file_size': file_size, - }, context=context) - - def update(self, cr, uid, binary_id, value, field_key, context=None): - binary = self.browse(cr, uid, binary_id, context=context) - base_location = self._get_location(cr, uid) - # Hack for passing the field_key in the full path - location = (base_location, field_key) - self._file_delete(cr, uid, location, binary.store_fname) - if not value: - return None - fname = self._file_write(cr, uid, location, value) - file_size = len(value.decode('base64')) - self.write(cr, uid, binary_id, { - 'store_fname': fname, - 'file_size': file_size, - }, context=context) - return True - - def get_content(self, cr, uid, field_key, binary_id, context=None): - binary = self.browse(cr, uid, binary_id, context=context) - base_location = self._get_location(cr, uid) - # Hack for passing the field_key in the full path - location = (base_location, field_key) - return self._file_read(cr, uid, location, binary.store_fname) - - def _file_read(self, cr, uid, location, fname): - return self.pool['ir.attachment']._file_read(cr, uid, location, fname) - - def _file_write(self, cr, uid, location, value): - return self.pool['ir.attachment']._file_write(cr, uid, location, value) - - def _file_delete(self, cr, uid, location, fname): - return self.pool['ir.attachment']._file_delete(cr, uid, location, fname) - - -class IrAttachment(orm.Model): - _inherit = 'ir.attachment' - - def _full_path(self, cr, uid, location, path): - #TODO add the posibility to customise the field_key - #maybe we can add a the field key in the ir.config.parameter - #and then retrieve an new path base on the config? - - # Hack for passing the field_key in the full path - if isinstance(location, tuple): - base_location, field_key = location - path = os.path.join(field_key, path) - else: - base_location = location - return super(IrAttachment, self).\ - _full_path(cr, uid, base_location, path) diff --git a/binary_field/fields.py b/binary_field/fields.py index 8f97c9b9dd0..d0bba39eea2 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -22,10 +22,98 @@ from openerp.osv import fields, orm from openerp.tools import image_resize_image +from openerp.tools.translate import _ +import os import logging + _logger = logging.getLogger(__name__) +class BinaryBinary(orm.Model): + _name = 'binary.binary' + _description = 'binary.binary' + + _columns = { + #'name': fields.char('Name',size=256, required=True), + 'store_fname': fields.char('Stored Filename', size=256), + #'extension': fields.char('Extension', size=4), + 'file_size': fields.integer('File Size'), + } + + def _get_location(self, cr, uid): + base_location = self.pool.get('ir.config_parameter').\ + get_param(cr, uid, 'binary.location') + if not base_location: + raise orm.except_orm( + _('Configuration Error'), + _('The "binary.location" is empty, please fill it in' + 'Configuration > Parameters > System Parameters')) + return base_location + + def add(self, cr, uid, value, field_key, context=None): + if not value: + return None + base_location = self._get_location(cr, uid) + # Hack for passing the field_key in the full path + location = (base_location, field_key) + file_size = len(value.decode('base64')) + fname = self._file_write(cr, uid, location, value) + return self.create(cr, uid, { + 'store_fname': fname, + 'file_size': file_size, + }, context=context) + + def update(self, cr, uid, binary_id, value, field_key, context=None): + binary = self.browse(cr, uid, binary_id, context=context) + base_location = self._get_location(cr, uid) + # Hack for passing the field_key in the full path + location = (base_location, field_key) + self._file_delete(cr, uid, location, binary.store_fname) + if not value: + return None + fname = self._file_write(cr, uid, location, value) + file_size = len(value.decode('base64')) + self.write(cr, uid, binary_id, { + 'store_fname': fname, + 'file_size': file_size, + }, context=context) + return True + + def get_content(self, cr, uid, field_key, binary_id, context=None): + binary = self.browse(cr, uid, binary_id, context=context) + base_location = self._get_location(cr, uid) + # Hack for passing the field_key in the full path + location = (base_location, field_key) + return self._file_read(cr, uid, location, binary.store_fname) + + def _file_read(self, cr, uid, location, fname): + return self.pool['ir.attachment']._file_read(cr, uid, location, fname) + + def _file_write(self, cr, uid, location, value): + return self.pool['ir.attachment']._file_write(cr, uid, location, value) + + def _file_delete(self, cr, uid, location, fname): + return self.pool['ir.attachment']._file_delete(cr, uid, location, fname) + + +class IrAttachment(orm.Model): + _inherit = 'ir.attachment' + + def _full_path(self, cr, uid, location, path): + #TODO add the posibility to customise the field_key + #maybe we can add a the field key in the ir.config.parameter + #and then retrieve an new path base on the config? + + # Hack for passing the field_key in the full path + if isinstance(location, tuple): + base_location, field_key = location + path = os.path.join(field_key, path) + else: + base_location = location + return super(IrAttachment, self).\ + _full_path(cr, uid, base_location, path) + + class BinaryField(fields.function): def __init__(self, string, filters=None, **kwargs): @@ -178,8 +266,8 @@ def __init__(self, pool, cr): additionnal_field = {} for field in self._columns: if isinstance(self._columns[field], BinaryField): - additionnal_field['%s_info_id' % field] = \ - fields.many2one('binary.binary', 'Binary') + additionnal_field['%s_uid' % field] = \ + fields.char('%s UID' % self._columns[field].string) #Inject the store invalidation function for ImageResize if isinstance(self._columns[field], ImageResizeField): From ba7b2bf4580ad718710745b57ce8da07d2e14f45 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Thu, 1 May 2014 23:58:16 +0200 Subject: [PATCH 10/25] [REF] refactor remove the "binary.binary" class --- binary_field/__init__.py | 1 - binary_field/fields.py | 183 ++++++++++++++++----------------------- 2 files changed, 76 insertions(+), 108 deletions(-) diff --git a/binary_field/__init__.py b/binary_field/__init__.py index 6ed503f6b73..b10caca2322 100644 --- a/binary_field/__init__.py +++ b/binary_field/__init__.py @@ -20,5 +20,4 @@ # ############################################################################### -from . import binary from . import fields diff --git a/binary_field/fields.py b/binary_field/fields.py index d0bba39eea2..872a8ffe2ff 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -29,18 +29,13 @@ _logger = logging.getLogger(__name__) -class BinaryBinary(orm.Model): - _name = 'binary.binary' - _description = 'binary.binary' - - _columns = { - #'name': fields.char('Name',size=256, required=True), - 'store_fname': fields.char('Stored Filename', size=256), - #'extension': fields.char('Extension', size=4), - 'file_size': fields.integer('File Size'), - } - - def _get_location(self, cr, uid): +class Storage(object): + + def __init__(self, cr, uid, obj, field_name): + self.cr = cr + self.uid = uid + self.pool = obj.pool + self.field_key = ("%s-%s" % (obj._name, field_name)).replace('.', '') base_location = self.pool.get('ir.config_parameter').\ get_param(cr, uid, 'binary.location') if not base_location: @@ -48,70 +43,32 @@ def _get_location(self, cr, uid): _('Configuration Error'), _('The "binary.location" is empty, please fill it in' 'Configuration > Parameters > System Parameters')) - return base_location + self.base_location = base_location + self.location = (self.base_location, self.field_key) - def add(self, cr, uid, value, field_key, context=None): + def add(self, value): if not value: - return None - base_location = self._get_location(cr, uid) - # Hack for passing the field_key in the full path - location = (base_location, field_key) + return {} file_size = len(value.decode('base64')) - fname = self._file_write(cr, uid, location, value) - return self.create(cr, uid, { - 'store_fname': fname, + binary_uid = self.pool['ir.attachment'].\ + _file_write(self.cr, self.uid, self.location, value) + _logger.debug('Add binary %s/%s' % (self.field_key, binary_uid)) + return { + 'binary_uid': binary_uid, 'file_size': file_size, - }, context=context) + } - def update(self, cr, uid, binary_id, value, field_key, context=None): - binary = self.browse(cr, uid, binary_id, context=context) - base_location = self._get_location(cr, uid) - # Hack for passing the field_key in the full path - location = (base_location, field_key) - self._file_delete(cr, uid, location, binary.store_fname) + def update(self, binary_uid, value): + _logger.debug('Delete binary %s/%s' % (self.field_key, binary_uid)) + self.pool['ir.attachment'].\ + _file_delete(self.cr, self.uid, self.location, binary_uid) if not value: - return None - fname = self._file_write(cr, uid, location, value) - file_size = len(value.decode('base64')) - self.write(cr, uid, binary_id, { - 'store_fname': fname, - 'file_size': file_size, - }, context=context) - return True + return {} + return self.add(value) - def get_content(self, cr, uid, field_key, binary_id, context=None): - binary = self.browse(cr, uid, binary_id, context=context) - base_location = self._get_location(cr, uid) - # Hack for passing the field_key in the full path - location = (base_location, field_key) - return self._file_read(cr, uid, location, binary.store_fname) - - def _file_read(self, cr, uid, location, fname): - return self.pool['ir.attachment']._file_read(cr, uid, location, fname) - - def _file_write(self, cr, uid, location, value): - return self.pool['ir.attachment']._file_write(cr, uid, location, value) - - def _file_delete(self, cr, uid, location, fname): - return self.pool['ir.attachment']._file_delete(cr, uid, location, fname) - - -class IrAttachment(orm.Model): - _inherit = 'ir.attachment' - - def _full_path(self, cr, uid, location, path): - #TODO add the posibility to customise the field_key - #maybe we can add a the field key in the ir.config.parameter - #and then retrieve an new path base on the config? - - # Hack for passing the field_key in the full path - if isinstance(location, tuple): - base_location, field_key = location - path = os.path.join(field_key, path) - else: - base_location = location - return super(IrAttachment, self).\ - _full_path(cr, uid, base_location, path) + def get(self, binary_uid): + return self.pool['ir.attachment'].\ + _file_read(self.cr, self.uid, self.location, binary_uid) class BinaryField(fields.function): @@ -128,51 +85,37 @@ def __init__(self, string, filters=None, **kwargs): self.filters = filters super(BinaryField, self).__init__(**new_kwargs) - def _get_binary_id(self, cr, uid, obj, field_name, record_id, context=None): - cr.execute( - "SELECT " + field_name + "_info_id FROM " - + obj._table + " WHERE id = %s", - (record_id,)) - return cr.fetchone()[0] - - def _build_field_key(self, obj, field_name): - return "%s-%s" % (obj._name, field_name) - - def _get_binary(self, cr, uid, obj, field_name, record_id, context=None): - binary_obj = obj.pool['binary.binary'] - binary_id = self._get_binary_id( - cr, uid, obj, field_name, record_id, context=context) - if not binary_id: - return None - field_key = self._build_field_key(obj, field_name) - return binary_obj.get_content( - cr, uid, field_key, binary_id, context=context) + def _prepare_binary_meta(self, cr, uid, field_name, res, context=None): + return { + '%s_uid' % field_name: res.get('binary_uid'), + '%s_file_size' % field_name: res.get('file_size'), + } def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, context=None): + storage = Storage(cr, uid, obj, field_name) if not isinstance(ids, (list, tuple)): ids = [ids] - binary_obj = obj.pool['binary.binary'] - for record_id in ids: - field_key = self._build_field_key(obj, field_name) - binary_id = self._get_binary_id( - cr, uid, obj, field_name, record_id, context=context) - if binary_id: - res_id = binary_obj.update( - cr, uid, binary_id, value, field_key, context=context) + for record in obj.browse(cr, uid, ids, context=context): + binary_uid = record['%s_uid' % field_name] + if binary_uid: + res = storage.update(binary_uid, value) else: - res_id = binary_obj.add( - cr, uid, value, field_key, context=context) - obj.write(cr, uid, record_id, { - '%s_info_id' % field_name: res_id, - }, context=context) + res = storage.add(value) + vals = self._prepare_binary_meta(cr, uid, field_name, res, context=context) + record.write(vals) return True def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): result = {} - for record_id in ids: - result[record_id] = self._get_binary( - cr, uid, obj, field_name, record_id, context=context) + storage = Storage(cr, uid, obj, field_name) + for record in obj.browse(cr, uid, ids, context=context): + binary_uid = record['%s_uid' % field_name] + if binary_uid: + result[record.id] = storage.get(binary_uid) + else: + result[record.id] = None + #TODO do not forget to add this for compatibility with binary field # if val and context.get('bin_size_%s' % name, context.get('bin_size')): @@ -262,12 +205,16 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, def __init__(self, pool, cr): original__init__(self, pool, cr) - if self.pool.get('binary.binary'): + if self.pool.get('binary.field.installed'): additionnal_field = {} for field in self._columns: if isinstance(self._columns[field], BinaryField): - additionnal_field['%s_uid' % field] = \ - fields.char('%s UID' % self._columns[field].string) + additionnal_field.update({ + '%s_uid' % field: + fields.char('%s UID' % self._columns[field].string), + '%s_file_size' % field: + fields.char('%s File Size' % self._columns[field].string), + }) #Inject the store invalidation function for ImageResize if isinstance(self._columns[field], ImageResizeField): @@ -281,3 +228,25 @@ def __init__(self, pool, cr): orm.BaseModel.__init__ = __init__ + + +class IrAttachment(orm.Model): + _inherit = 'ir.attachment' + + def _full_path(self, cr, uid, location, path): + #TODO add the posibility to customise the field_key + #maybe we can add a the field key in the ir.config.parameter + #and then retrieve an new path base on the config? + + # Hack for passing the field_key in the full path + if isinstance(location, tuple): + base_location, field_key = location + path = os.path.join(field_key, path) + else: + base_location = location + return super(IrAttachment, self).\ + _full_path(cr, uid, base_location, path) + + +class BinaryFieldInstalled(orm.AbstractModel): + _name = 'binary.field.installed' From 969075f8c1bf0280ff4f44bc6c713656a84742c1 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Fri, 2 May 2014 01:19:08 +0200 Subject: [PATCH 11/25] [IMP] add the support of the option "bin_size" in the context --- binary_field/fields.py | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/binary_field/fields.py b/binary_field/fields.py index 872a8ffe2ff..b59b1c7cf93 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -23,7 +23,9 @@ from openerp.osv import fields, orm from openerp.tools import image_resize_image from openerp.tools.translate import _ +from openerp import tools import os +import sys import logging _logger = logging.getLogger(__name__) @@ -49,7 +51,7 @@ def __init__(self, cr, uid, obj, field_name): def add(self, value): if not value: return {} - file_size = len(value.decode('base64')) + file_size = sys.getsizeof(value.decode('base64')) binary_uid = self.pool['ir.attachment'].\ _file_write(self.cr, self.uid, self.location, value) _logger.debug('Add binary %s/%s' % (self.field_key, binary_uid)) @@ -85,6 +87,11 @@ def __init__(self, string, filters=None, **kwargs): self.filters = filters super(BinaryField, self).__init__(**new_kwargs) + #No postprocess are needed + #we already take care of bin_size option in the context + def postprocess(self, cr, uid, obj, field, value=None, context=None): + return value + def _prepare_binary_meta(self, cr, uid, field_name, res, context=None): return { '%s_uid' % field_name: res.get('binary_uid'), @@ -112,16 +119,14 @@ def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): for record in obj.browse(cr, uid, ids, context=context): binary_uid = record['%s_uid' % field_name] if binary_uid: - result[record.id] = storage.get(binary_uid) + #Compatibility with existing binary field + if context.get('bin_size_%s' % field_name, context.get('bin_size')): + size = record['%s_file_size' % field_name] + result[record.id] = tools.human_size(long(size)) + else: + result[record.id] = storage.get(binary_uid) else: result[record.id] = None - - -#TODO do not forget to add this for compatibility with binary field -# if val and context.get('bin_size_%s' % name, context.get('bin_size')): -# res[i] = tools.human_size(long(val)) -# else: -# res[i] = val return result @@ -157,20 +162,11 @@ def __init__(self, string, related_field, height, width, string=string, **kwargs) - def _get_binary(self, cr, uid, obj, field_name, record_id, context=None): - #Idée via le fonction store, je trigger le write - #et la en fonciton du mode soit simplement je vire les images - #soit je les vire et je les regénère - if context.get('bin_size'): - return 2 - return super(ImageResizeField, self)._get_binary( - cr, uid, obj, field_name, record_id, context=context) - def _refresh_cache(self, obj, cr, uid, ids, field_name, context=None): if not isinstance(ids, (list, tuple)): ids = [ids] for record_id in ids: - _logger.debug('Refresh Image Cache from the field %s of object %s ' + _logger.debug('Refreshing Image Cache from the field %s of object %s ' 'id : %s' % (field_name, obj._name, record_id)) field = obj._columns[field_name] record = obj.browse(cr, uid, record_id, context=context) @@ -234,11 +230,11 @@ class IrAttachment(orm.Model): _inherit = 'ir.attachment' def _full_path(self, cr, uid, location, path): - #TODO add the posibility to customise the field_key - #maybe we can add a the field key in the ir.config.parameter - #and then retrieve an new path base on the config? - # Hack for passing the field_key in the full path + # For now I prefer to use this hack and to reuse + # the ir.attachment code + # An alternative way will to copy/paste and + # adapt the ir.attachment code if isinstance(location, tuple): base_location, field_key = location path = os.path.join(field_key, path) From 061bed26945893ecec9e8f381fe53cf6d7ab6cc7 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Fri, 2 May 2014 01:32:22 +0200 Subject: [PATCH 12/25] [IMP] give the possibility to pass a custom Storage class. This class can be initilized with the information of the record. So you can even customise the path of the storage depending of the property of the record --- binary_field/fields.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/binary_field/fields.py b/binary_field/fields.py index b59b1c7cf93..ebc3d451476 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -33,11 +33,11 @@ class Storage(object): - def __init__(self, cr, uid, obj, field_name): + def __init__(self, cr, uid, record, field_name): self.cr = cr self.uid = uid - self.pool = obj.pool - self.field_key = ("%s-%s" % (obj._name, field_name)).replace('.', '') + self.pool = record._model.pool + self.field_key = ("%s-%s" % (record._name, field_name)).replace('.', '') base_location = self.pool.get('ir.config_parameter').\ get_param(cr, uid, 'binary.location') if not base_location: @@ -75,7 +75,7 @@ def get(self, binary_uid): class BinaryField(fields.function): - def __init__(self, string, filters=None, **kwargs): + def __init__(self, string, filters=None, get_storage=Storage, **kwargs): new_kwargs = { 'type': 'binary', 'string': string, @@ -85,6 +85,7 @@ def __init__(self, string, filters=None, **kwargs): } new_kwargs.update(kwargs) self.filters = filters + self.get_storage = get_storage super(BinaryField, self).__init__(**new_kwargs) #No postprocess are needed @@ -100,10 +101,10 @@ def _prepare_binary_meta(self, cr, uid, field_name, res, context=None): def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, context=None): - storage = Storage(cr, uid, obj, field_name) if not isinstance(ids, (list, tuple)): ids = [ids] for record in obj.browse(cr, uid, ids, context=context): + storage = self.get_storage(cr, uid, record, field_name) binary_uid = record['%s_uid' % field_name] if binary_uid: res = storage.update(binary_uid, value) @@ -115,8 +116,8 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): result = {} - storage = Storage(cr, uid, obj, field_name) for record in obj.browse(cr, uid, ids, context=context): + storage = self.get_storage(cr, uid, record, field_name) binary_uid = record['%s_uid' % field_name] if binary_uid: #Compatibility with existing binary field From a7b49f1376722cbaeba9adf01351b0ca99ec4587 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Fri, 6 Jun 2014 12:33:09 +0200 Subject: [PATCH 13/25] [FIX] fix typo, missing context and use interger for file size --- binary_field/fields.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/binary_field/fields.py b/binary_field/fields.py index ebc3d451476..5603aa2bacc 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -164,6 +164,8 @@ def __init__(self, string, related_field, height, width, **kwargs) def _refresh_cache(self, obj, cr, uid, ids, field_name, context=None): + if context is None: + context = {} if not isinstance(ids, (list, tuple)): ids = [ids] for record_id in ids: @@ -203,25 +205,25 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, def __init__(self, pool, cr): original__init__(self, pool, cr) if self.pool.get('binary.field.installed'): - additionnal_field = {} + additional_field = {} for field in self._columns: if isinstance(self._columns[field], BinaryField): - additionnal_field.update({ + additional_field.update({ '%s_uid' % field: fields.char('%s UID' % self._columns[field].string), '%s_file_size' % field: - fields.char('%s File Size' % self._columns[field].string), + fields.integer('%s File Size' % self._columns[field].string), }) #Inject the store invalidation function for ImageResize if isinstance(self._columns[field], ImageResizeField): self._columns[field].store = { self._name: ( - lambda self, cr, uid, ids, c={}: ids, + lambda self, cr, uid, ids, c=None: ids, [self._columns[field].related_field], 10), } - self._columns.update(additionnal_field) + self._columns.update(additional_field) orm.BaseModel.__init__ = __init__ From eceea5d800283128d4fe9a2a4f6030e2d6eb6e82 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Fri, 6 Jun 2014 12:58:21 +0200 Subject: [PATCH 14/25] [IMP] add the params config so you can pass some special config to your Storage Class --- binary_field/fields.py | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/binary_field/fields.py b/binary_field/fields.py index 5603aa2bacc..6e2aa0e2095 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -33,19 +33,24 @@ class Storage(object): - def __init__(self, cr, uid, record, field_name): + def __init__(self, cr, uid, record, field_name, config=None): self.cr = cr self.uid = uid self.pool = record._model.pool - self.field_key = ("%s-%s" % (record._name, field_name)).replace('.', '') - base_location = self.pool.get('ir.config_parameter').\ - get_param(cr, uid, 'binary.location') - if not base_location: - raise orm.except_orm( - _('Configuration Error'), - _('The "binary.location" is empty, please fill it in' - 'Configuration > Parameters > System Parameters')) - self.base_location = base_location + if config and config.get('field_key'): + self.field_key = config['field_key'] + else: + self.field_key = ("%s-%s" % (record._name, field_name)).replace('.', '') + if config and config.get('base_location'): + self.base_location = config['base_location'] + else: + self.base_location = self.pool.get('ir.config_parameter').\ + get_param(cr, uid, 'binary.location') + if not self.base_location: + raise orm.except_orm( + _('Configuration Error'), + _('The "binary.location" is empty, please fill it in' + 'Configuration > Parameters > System Parameters')) self.location = (self.base_location, self.field_key) def add(self, value): @@ -75,7 +80,8 @@ def get(self, binary_uid): class BinaryField(fields.function): - def __init__(self, string, filters=None, get_storage=Storage, **kwargs): + def __init__(self, string, filters=None, get_storage=Storage, + config=None, **kwargs): new_kwargs = { 'type': 'binary', 'string': string, @@ -86,6 +92,7 @@ def __init__(self, string, filters=None, get_storage=Storage, **kwargs): new_kwargs.update(kwargs) self.filters = filters self.get_storage = get_storage + self.config = config super(BinaryField, self).__init__(**new_kwargs) #No postprocess are needed @@ -104,7 +111,8 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, if not isinstance(ids, (list, tuple)): ids = [ids] for record in obj.browse(cr, uid, ids, context=context): - storage = self.get_storage(cr, uid, record, field_name) + storage = self.get_storage(cr, uid, record, field_name, + config=self.config) binary_uid = record['%s_uid' % field_name] if binary_uid: res = storage.update(binary_uid, value) @@ -117,7 +125,8 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): result = {} for record in obj.browse(cr, uid, ids, context=context): - storage = self.get_storage(cr, uid, record, field_name) + storage = self.get_storage(cr, uid, record, field_name, + config=self.config) binary_uid = record['%s_uid' % field_name] if binary_uid: #Compatibility with existing binary field From 42a6bed82add90c6de0d86179c81e4df9bbb2db0 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Fri, 6 Jun 2014 13:03:04 +0200 Subject: [PATCH 15/25] [IMP] Update example module --- binary_field_example/res_company.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/binary_field_example/res_company.py b/binary_field_example/res_company.py index 56414c15b02..370a58bfb0f 100644 --- a/binary_field_example/res_company.py +++ b/binary_field_example/res_company.py @@ -28,7 +28,11 @@ class ResCompany(orm.Model): _columns = { 'binary_test': fields.BinaryField('Test Binary'), - 'image_test': fields.ImageField('Test Image'), + 'image_test': fields.ImageField('Test Image', + config={ + 'field_key': 'StoreMeHere', + 'base_location': 'file:///testpath', + }), 'image_test_resize': fields.ImageResizeField( related_field='image_test', string='Test Image small', From 0dc821d7cc75bebd16ce856e045507b22629aa0c Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Sun, 8 Jun 2014 23:37:24 +0200 Subject: [PATCH 16/25] [REF] remove useless option filters as this option have been drop with the gtk client --- binary_field/fields.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/binary_field/fields.py b/binary_field/fields.py index 6e2aa0e2095..f81d48ab5c5 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -80,8 +80,7 @@ def get(self, binary_uid): class BinaryField(fields.function): - def __init__(self, string, filters=None, get_storage=Storage, - config=None, **kwargs): + def __init__(self, string, get_storage=Storage, config=None, **kwargs): new_kwargs = { 'type': 'binary', 'string': string, @@ -90,7 +89,6 @@ def __init__(self, string, filters=None, get_storage=Storage, 'multi': False, } new_kwargs.update(kwargs) - self.filters = filters self.get_storage = get_storage self.config = config super(BinaryField, self).__init__(**new_kwargs) @@ -142,12 +140,6 @@ def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): class ImageField(BinaryField): - def __init__(self, string, filters=None, **kwargs): - self.filters = filters - super(ImageField, self).__init__( - string=string, - **kwargs) - def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, context=None): super(ImageField, self)._fnct_write( @@ -162,9 +154,7 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, class ImageResizeField(ImageField): - def __init__(self, string, related_field, height, width, - filters=None, **kwargs): - self.filters = filters + def __init__(self, string, related_field, height, width, **kwargs): self.height = height self.width = width self.related_field = related_field From abcbaf112c8847f1edf0bfd8591932b8a48dbd0e Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Sun, 8 Jun 2014 23:45:23 +0200 Subject: [PATCH 17/25] [IMP] update description and add doc string --- binary_field/__openerp__.py | 35 +++++++++++++++++++++++++++++++++-- binary_field/fields.py | 26 ++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/binary_field/__openerp__.py b/binary_field/__openerp__.py index c24e4f0224f..324a243ffb1 100644 --- a/binary_field/__openerp__.py +++ b/binary_field/__openerp__.py @@ -27,13 +27,44 @@ 'license': 'AGPL-3', 'category': 'Framework', 'description': """This module extend the fields class in order to add 3 new -type of fields +type of fields. - BinaryStore - ImageStore - ImageRezise -Now let's do it ;) +All of this fields will be store on the file system by default and not in the +database. If you want to store it on an other support (database, S3, ftp, SFTP...) +Then you should create your own 'storage class' and use your custom 'storage +class' instead + +The default Storage class will store the field on the file system and build +the path like that + BASE_LOCATION/DB_NAME/MODEL-FIELD/XXX/YYYYY + +with + +- BASE_LOCATION: the base location configured in ir.config_parameter +- DB_NAME: your database name +- MODEL-FIELD: the concatenation of the name of the model with the name of the +field, for example 'product_product-image' +- XXX: the first 3 letter of the file name build with their sha1 hash +- YYYYYY: file name build with their sha1 hash + +Here is an example of field declaration + + 'binary_test': fields.BinaryField('Test Binary'), + 'image_test': fields.ImageField('Test Image', + config={ + 'field_key': 'StoreMeHere', + 'base_location': 'file:///testpath', + }), + 'image_test_resize': fields.ImageResizeField( + related_field='image_test', + string='Test Image small', + height=64, + width=64, + ), """, 'depends': [ 'base', diff --git a/binary_field/fields.py b/binary_field/fields.py index f81d48ab5c5..1a9ce81aab1 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -81,6 +81,16 @@ def get(self, binary_uid): class BinaryField(fields.function): def __init__(self, string, get_storage=Storage, config=None, **kwargs): + """Init a BinaryField field + :params string: Name of the field + :type string: str + :params get_storage: Storage Class for processing the field + by default use the Storage on filesystem + :type get_storage: :py:class`binary_field.Storage' + :params config: configuration used by the storage class + :type config: what you want it's depend of the Storage class + implementation + """ new_kwargs = { 'type': 'binary', 'string': string, @@ -155,6 +165,22 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, class ImageResizeField(ImageField): def __init__(self, string, related_field, height, width, **kwargs): + """Init a BinaryField field + :params string: Name of the field + :type string: str + :params related_field: reference field that should be resized + :type related_field: str + :params height: height of the image resized + :type height: integer + :params width: width of the image resized + :type width: integer + :params get_storage: Storage Class for processing the field + by default use the Storage on filesystem + :type get_storage: :py:class`binary_field.Storage' + :params config: configuration used by the storage class + :type config: what you want it's depend of the Storage class + implementation + """ self.height = height self.width = width self.related_field = related_field From 8a7665fb0902ca048c3e6f7f3199eb0395f448af Mon Sep 17 00:00:00 2001 From: Lorenzo Battistini Date: Fri, 20 Jun 2014 16:14:52 +0200 Subject: [PATCH 18/25] [IMP] PEP8 --- binary_field/__init__.py | 2 +- binary_field/__openerp__.py | 15 ++++++--------- binary_field/fields.py | 27 ++++++++++++++++----------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/binary_field/__init__.py b/binary_field/__init__.py index b10caca2322..8542eda8f04 100644 --- a/binary_field/__init__.py +++ b/binary_field/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- ############################################################################### # -# Module for OpenERP +# Module for OpenERP # Copyright (C) 2013 Akretion (http://www.akretion.com). # @author Sébastien BEAU # diff --git a/binary_field/__openerp__.py b/binary_field/__openerp__.py index 324a243ffb1..1c9bf6acd23 100644 --- a/binary_field/__openerp__.py +++ b/binary_field/__openerp__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- ############################################################################### # -# Module for OpenERP +# Module for OpenERP # Copyright (C) 2013 Akretion (http://www.akretion.com). # @author Sébastien BEAU # @@ -33,13 +33,14 @@ - ImageRezise All of this fields will be store on the file system by default and not in the -database. If you want to store it on an other support (database, S3, ftp, SFTP...) -Then you should create your own 'storage class' and use your custom 'storage +database. If you want to store it on an other support (database, S3, ftp, +SFTP...) +Then you should create your own 'storage class' and use your custom 'storage class' instead -The default Storage class will store the field on the file system and build +The default Storage class will store the field on the file system and build the path like that - + BASE_LOCATION/DB_NAME/MODEL-FIELD/XXX/YYYYY with @@ -75,7 +76,3 @@ 'installable': True, 'application': True, } - - - - diff --git a/binary_field/fields.py b/binary_field/fields.py index 1a9ce81aab1..8afb1f60990 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -40,7 +40,8 @@ def __init__(self, cr, uid, record, field_name, config=None): if config and config.get('field_key'): self.field_key = config['field_key'] else: - self.field_key = ("%s-%s" % (record._name, field_name)).replace('.', '') + self.field_key = ( + "%s-%s" % (record._name, field_name)).replace('.', '') if config and config.get('base_location'): self.base_location = config['base_location'] else: @@ -103,8 +104,8 @@ def __init__(self, string, get_storage=Storage, config=None, **kwargs): self.config = config super(BinaryField, self).__init__(**new_kwargs) - #No postprocess are needed - #we already take care of bin_size option in the context + # No postprocess are needed + # we already take care of bin_size option in the context def postprocess(self, cr, uid, obj, field, value=None, context=None): return value @@ -126,7 +127,8 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, res = storage.update(binary_uid, value) else: res = storage.add(value) - vals = self._prepare_binary_meta(cr, uid, field_name, res, context=context) + vals = self._prepare_binary_meta( + cr, uid, field_name, res, context=context) record.write(vals) return True @@ -137,8 +139,10 @@ def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): config=self.config) binary_uid = record['%s_uid' % field_name] if binary_uid: - #Compatibility with existing binary field - if context.get('bin_size_%s' % field_name, context.get('bin_size')): + # Compatibility with existing binary field + if context.get( + 'bin_size_%s' % field_name, context.get('bin_size') + ): size = record['%s_file_size' % field_name] result[record.id] = tools.human_size(long(size)) else: @@ -194,8 +198,8 @@ def _refresh_cache(self, obj, cr, uid, ids, field_name, context=None): if not isinstance(ids, (list, tuple)): ids = [ids] for record_id in ids: - _logger.debug('Refreshing Image Cache from the field %s of object %s ' - 'id : %s' % (field_name, obj._name, record_id)) + _logger.debug('Refreshing Image Cache from the field %s of object ' + '%s id : %s' % (field_name, obj._name, record_id)) field = obj._columns[field_name] record = obj.browse(cr, uid, record_id, context=context) original_image = record[field.related_field] @@ -237,10 +241,11 @@ def __init__(self, pool, cr): '%s_uid' % field: fields.char('%s UID' % self._columns[field].string), '%s_file_size' % field: - fields.integer('%s File Size' % self._columns[field].string), + fields.integer( + '%s File Size' % self._columns[field].string), }) - #Inject the store invalidation function for ImageResize + # Inject the store invalidation function for ImageResize if isinstance(self._columns[field], ImageResizeField): self._columns[field].store = { self._name: ( @@ -261,7 +266,7 @@ def _full_path(self, cr, uid, location, path): # Hack for passing the field_key in the full path # For now I prefer to use this hack and to reuse # the ir.attachment code - # An alternative way will to copy/paste and + # An alternative way will to copy/paste and # adapt the ir.attachment code if isinstance(location, tuple): base_location, field_key = location From 36cedb920ce9287bd594127aacfda58327ba15f7 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Tue, 1 Jul 2014 09:13:24 +0200 Subject: [PATCH 19/25] [REF] refcactor code, merge the class ImageFieldResize into the class ImageField --- binary_field/fields.py | 109 ++++++++++++++-------------- binary_field_example/res_company.py | 12 +-- 2 files changed, 59 insertions(+), 62 deletions(-) diff --git a/binary_field/fields.py b/binary_field/fields.py index 8afb1f60990..64f2599471f 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -95,14 +95,13 @@ def __init__(self, string, get_storage=Storage, config=None, **kwargs): new_kwargs = { 'type': 'binary', 'string': string, - 'fnct': self._fnct_read, 'fnct_inv': self._fnct_write, 'multi': False, } new_kwargs.update(kwargs) self.get_storage = get_storage self.config = config - super(BinaryField, self).__init__(**new_kwargs) + super(BinaryField, self).__init__(self._fnct_read, **new_kwargs) # No postprocess are needed # we already take care of bin_size option in the context @@ -154,45 +153,64 @@ def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): class ImageField(BinaryField): - def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, - context=None): - super(ImageField, self)._fnct_write( - obj, cr, uid, ids, field_name, value, args, context=context) - for name, field in obj._columns.items(): - if isinstance(field, ImageResizeField) \ - and field.related_field == field_name: - field._refresh_cache( - obj, cr, uid, ids, name, context=context) - return True - - -class ImageResizeField(ImageField): - - def __init__(self, string, related_field, height, width, **kwargs): - """Init a BinaryField field + def __init__(self, string, get_storage=Storage, config=None, + resize_based_on=None, height=64, width=64, **kwargs): + """Init a ImageField field :params string: Name of the field :type string: str - :params related_field: reference field that should be resized - :type related_field: str - :params height: height of the image resized - :type height: integer - :params width: width of the image resized - :type width: integer :params get_storage: Storage Class for processing the field by default use the Storage on filesystem :type get_storage: :py:class`binary_field.Storage' :params config: configuration used by the storage class :type config: what you want it's depend of the Storage class implementation + :params resize_based_on: reference field that should be resized + :type resize_based_on: str + :params height: height of the image resized + :type height: integer + :params width: width of the image resized + :type width: integer """ + super(ImageField, self).__init__( + string, + get_storage=get_storage, + config=config, + **kwargs) + self.resize_based_on = resize_based_on self.height = height self.width = width - self.related_field = related_field - super(ImageResizeField, self).__init__( - string=string, - **kwargs) + + def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, + context=None): + if context is None: + context = {} + related_field_name = obj._columns[field_name].resize_based_on + + # If we write an original image in a field with the option resized + # We have to store the image on the related field and not on the + # resized image field + if related_field_name and not context.get('refresh_image_cache'): + return self._fnct_write( + obj, cr, uid, ids, related_field_name, value, args, + context=context) + else: + super(ImageField, self)._fnct_write( + obj, cr, uid, ids, field_name, value, args, context=context) + + for name, field in obj._columns.items(): + if isinstance(field, ImageField)\ + and field.resize_based_on == field_name: + field._refresh_cache( + obj, cr, uid, ids, name, context=context) + return True def _refresh_cache(self, obj, cr, uid, ids, field_name, context=None): + """Refresh the cache of the small image + :params ids: list of object id to refresh + :type ids: list + :params field_name: Name of the field to refresh the cache + :type field_name: str + """ if context is None: context = {} if not isinstance(ids, (list, tuple)): @@ -202,7 +220,7 @@ def _refresh_cache(self, obj, cr, uid, ids, field_name, context=None): '%s id : %s' % (field_name, obj._name, record_id)) field = obj._columns[field_name] record = obj.browse(cr, uid, record_id, context=context) - original_image = record[field.related_field] + original_image = record[field.resize_based_on] if original_image: size = (field.height, field.width) resized_image = image_resize_image(original_image, size) @@ -213,19 +231,9 @@ def _refresh_cache(self, obj, cr, uid, ids, field_name, context=None): self._fnct_write(obj, cr, uid, [record_id], field_name, resized_image, None, context=ctx) - def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, - context=None): - if context is not None and context.get('refresh_image_cache'): - field = field_name - else: - field = obj._columns[field_name].related_field - return super(ImageResizeField, self)._fnct_write( - obj, cr, uid, ids, field, value, args, context=context) - fields.BinaryField = BinaryField fields.ImageField = ImageField -fields.ImageResizeField = ImageResizeField original__init__ = orm.BaseModel.__init__ @@ -235,24 +243,17 @@ def __init__(self, pool, cr): original__init__(self, pool, cr) if self.pool.get('binary.field.installed'): additional_field = {} - for field in self._columns: - if isinstance(self._columns[field], BinaryField): + for field_name in self._columns: + field = self._columns[field_name] + if isinstance(field, BinaryField): additional_field.update({ - '%s_uid' % field: - fields.char('%s UID' % self._columns[field].string), - '%s_file_size' % field: + '%s_uid' % field_name: + fields.char('%s UID' % self._columns[field_name].string), + '%s_file_size' % field_name: fields.integer( - '%s File Size' % self._columns[field].string), + '%s File Size' % self._columns[field_name].string), }) - - # Inject the store invalidation function for ImageResize - if isinstance(self._columns[field], ImageResizeField): - self._columns[field].store = { - self._name: ( - lambda self, cr, uid, ids, c=None: ids, - [self._columns[field].related_field], - 10), - } + #import pdb; pdb.set_trace() self._columns.update(additional_field) diff --git a/binary_field_example/res_company.py b/binary_field_example/res_company.py index 370a58bfb0f..12310a27d9e 100644 --- a/binary_field_example/res_company.py +++ b/binary_field_example/res_company.py @@ -28,14 +28,10 @@ class ResCompany(orm.Model): _columns = { 'binary_test': fields.BinaryField('Test Binary'), - 'image_test': fields.ImageField('Test Image', - config={ - 'field_key': 'StoreMeHere', - 'base_location': 'file:///testpath', - }), - 'image_test_resize': fields.ImageResizeField( - related_field='image_test', - string='Test Image small', + 'image_test': fields.ImageField('Test Image'), + 'image_test_resize': fields.ImageField( + 'Test Image small', + resize_based_on='image_test', height=64, width=64, ), From 8bf91b6c418925e0090f38bdbb313623d22dc6d1 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Tue, 1 Jul 2014 14:53:10 +0200 Subject: [PATCH 20/25] [REF] refactor storage, add a new object "storage.configuration" that allow user to customise the storage configuration per field. Also base the image file storage on odoo V8 code --- binary_field/< | 274 +++++++++++++++++++++++++++++++++ binary_field/__init__.py | 2 + binary_field/__openerp__.py | 2 + binary_field/data.xml | 8 +- binary_field/fields.py | 128 +++++++++------ binary_field/ir_model.py | 35 +++++ binary_field/ir_model_view.xml | 26 ++++ binary_field/storage.py | 117 ++++++++++++++ binary_field/storage_view.xml | 65 ++++++++ 9 files changed, 604 insertions(+), 53 deletions(-) create mode 100644 binary_field/< create mode 100644 binary_field/ir_model.py create mode 100644 binary_field/ir_model_view.xml create mode 100644 binary_field/storage.py create mode 100644 binary_field/storage_view.xml diff --git a/binary_field/< b/binary_field/< new file mode 100644 index 00000000000..96f0f669812 --- /dev/null +++ b/binary_field/< @@ -0,0 +1,274 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2014 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################### + +from openerp.osv import fields, orm +from openerp.tools import image_resize_image +from openerp.tools.translate import _ +from openerp import tools +import os +import sys +import logging + +_logger = logging.getLogger(__name__) + + +class Storage(object): + + def __init__(self, cr, uid, record, field_name, config=None): + self.cr = cr + self.uid = uid + self.pool = record._model.pool + if config and config.get('field_key'): + self.field_key = config['field_key'] + else: + self.field_key = ( + "%s-%s" % (record._name, field_name)).replace('.', '') + if config and config.get('base_location'): + self.base_location = config['base_location'] + else: + self.base_location = self.pool.get('ir.config_parameter').\ + get_param(cr, uid, 'binary.location') + if not self.base_location: + raise orm.except_orm( + _('Configuration Error'), + _('The "binary.location" is empty, please fill it in' + 'Configuration > Parameters > System Parameters')) + self.location = (self.base_location, self.field_key) + + def add(self, value): + if not value: + return {} + file_size = sys.getsizeof(value.decode('base64')) + binary_uid = self.pool['ir.attachment'].\ + _file_write(self.cr, self.uid, self.location, value) + _logger.debug('Add binary %s/%s' % (self.field_key, binary_uid)) + return { + 'binary_uid': binary_uid, + 'file_size': file_size, + } + + def update(self, binary_uid, value): + _logger.debug('Delete binary %s/%s' % (self.field_key, binary_uid)) + self.pool['ir.attachment'].\ + _file_delete(self.cr, self.uid, self.location, binary_uid) + if not value: + return {} + return self.add(value) + + def get(self, binary_uid): + return self.pool['ir.attachment'].\ + _file_read(self.cr, self.uid, self.location, binary_uid) + + +class BinaryField(fields.function): + + def __init__(self, string, get_storage=Storage, config=None, **kwargs): + """Init a BinaryField field + :params string: Name of the field + :type string: str + :params get_storage: Storage Class for processing the field + by default use the Storage on filesystem + :type get_storage: :py:class`binary_field.Storage' + :params config: configuration used by the storage class + :type config: what you want it's depend of the Storage class + implementation + """ + new_kwargs = { + 'type': 'binary', + 'string': string, + 'fnct': self._fnct_read, + 'fnct_inv': self._fnct_write, + 'multi': False, + } + new_kwargs.update(kwargs) + self.get_storage = get_storage + self.config = config + super(BinaryField, self).__init__(**new_kwargs) + + # No postprocess are needed + # we already take care of bin_size option in the context + def postprocess(self, cr, uid, obj, field, value=None, context=None): + return value + + def _prepare_binary_meta(self, cr, uid, field_name, res, context=None): + return { + '%s_uid' % field_name: res.get('binary_uid'), + '%s_file_size' % field_name: res.get('file_size'), + } + + def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, + context=None): + if not isinstance(ids, (list, tuple)): + ids = [ids] + for record in obj.browse(cr, uid, ids, context=context): + storage = self.get_storage(cr, uid, record, field_name, + config=self.config) + binary_uid = record['%s_uid' % field_name] + if binary_uid: + res = storage.update(binary_uid, value) + else: + res = storage.add(value) + vals = self._prepare_binary_meta( + cr, uid, field_name, res, context=context) + record.write(vals) + return True + + def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): + result = {} + for record in obj.browse(cr, uid, ids, context=context): + storage = self.get_storage(cr, uid, record, field_name, + config=self.config) + binary_uid = record['%s_uid' % field_name] + if binary_uid: + # Compatibility with existing binary field + if context.get( + 'bin_size_%s' % field_name, context.get('bin_size') + ): + size = record['%s_file_size' % field_name] + result[record.id] = tools.human_size(long(size)) + else: + result[record.id] = storage.get(binary_uid) + else: + result[record.id] = None + return result + + +class ImageField(BinaryField): + + def __init__(self, string, get_storage=Storage, config=None, + resize_based_on=None, height=64, width=64, **kwargs): + """Init a ImageField field + :params string: Name of the field + :type string: str + :params get_storage: Storage Class for processing the field + by default use the Storage on filesystem + :type get_storage: :py:class`binary_field.Storage' + :params config: configuration used by the storage class + :type config: what you want it's depend of the Storage class + implementation + :params resize_based_on: reference field that should be resized + :type resize_based_on: str + :params height: height of the image resized + :type height: integer + :params width: width of the image resized + :type width: integer + """ + super(BinaryField, self).__init__( + string, + get_storage=get_storage, + config=config, + **kwargs) + self.resize_base_on = resize_based_on + + def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, + context=None): + if context is None: + context = {} + related_field_name = obj._columns[field_name].resize_based_on + + # If we write an original image in a field with the option resized + # We have to store the image on the related field and not on the + # resized image field + if related_field_name and not context.get('refresh_image_cache'): + return super(ImageField, self)._fnct_write( + obj, cr, uid, ids, related_field_name, value, args, + context=context) + else: + super(ImageField, self)._fnct_write( + obj, cr, uid, ids, field_name, value, args, context=context) + + for name, field in obj._columns.items(): + if field.resize_based_on == field_name: + field._refresh_cache( + obj, cr, uid, ids, name, context=context) + return True + + def _refresh_cache(self, obj, cr, uid, ids, field_name, context=None): + if context is None: + context = {} + if not isinstance(ids, (list, tuple)): + ids = [ids] + for record_id in ids: + _logger.debug('Refreshing Image Cache from the field %s of object ' + '%s id : %s' % (field_name, obj._name, record_id)) + field = obj._columns[field_name] + record = obj.browse(cr, uid, record_id, context=context) + original_image = record[field.related_field] + if original_image: + size = (field.height, field.width) + resized_image = image_resize_image(original_image, size) + else: + resized_image = None + ctx = context.copy() + ctx['refresh_image_cache'] = True + self._fnct_write(obj, cr, uid, [record_id], field_name, + resized_image, None, context=ctx) + + +fields.BinaryField = BinaryField +fields.ImageField = ImageField + + +original__init__ = orm.BaseModel.__init__ + + +def __init__(self, pool, cr): + original__init__(self, pool, cr) + if self.pool.get('binary.field.installed'): + additional_field = {} + for field_name in self._columns: + field = self._columns[field_name] + if isinstance(field, BinaryField): + additional_field.update({ + '%s_uid' % field_name: + fields.char('%s UID' % self._columns[field_name].string), + '%s_file_size' % field_name: + fields.integer( + '%s File Size' % self._columns[field_name].string), + }) + import pdb; pdb.set_trace() + self._columns.update(additional_field) + + +orm.BaseModel.__init__ = __init__ + + +class IrAttachment(orm.Model): + _inherit = 'ir.attachment' + + def _full_path(self, cr, uid, location, path): + # Hack for passing the field_key in the full path + # For now I prefer to use this hack and to reuse + # the ir.attachment code + # An alternative way will to copy/paste and + # adapt the ir.attachment code + if isinstance(location, tuple): + base_location, field_key = location + path = os.path.join(field_key, path) + else: + base_location = location + return super(IrAttachment, self).\ + _full_path(cr, uid, base_location, path) + + +class BinaryFieldInstalled(orm.AbstractModel): + _name = 'binary.field.installed' diff --git a/binary_field/__init__.py b/binary_field/__init__.py index 8542eda8f04..c9d434c4ab0 100644 --- a/binary_field/__init__.py +++ b/binary_field/__init__.py @@ -21,3 +21,5 @@ ############################################################################### from . import fields +from . import storage +from . import ir_model diff --git a/binary_field/__openerp__.py b/binary_field/__openerp__.py index 1c9bf6acd23..6ec325740cd 100644 --- a/binary_field/__openerp__.py +++ b/binary_field/__openerp__.py @@ -72,6 +72,8 @@ ], 'data': [ 'data.xml', + 'ir_model_view.xml', + 'storage_view.xml', ], 'installable': True, 'application': True, diff --git a/binary_field/data.xml b/binary_field/data.xml index 2f827b6fd39..226e68857df 100644 --- a/binary_field/data.xml +++ b/binary_field/data.xml @@ -1,9 +1,11 @@ - - binary.location - file:///filestore + + filesystem + Default File System Storage + openerp/filestore/ + 1 diff --git a/binary_field/fields.py b/binary_field/fields.py index 64f2599471f..727ceca32b5 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -20,6 +20,7 @@ # ############################################################################### +import hashlib from openerp.osv import fields, orm from openerp.tools import image_resize_image from openerp.tools.translate import _ @@ -33,55 +34,102 @@ class Storage(object): - def __init__(self, cr, uid, record, field_name, config=None): + def __init__(self, cr, uid, model_name, field_name, record, config): self.cr = cr self.uid = uid self.pool = record._model.pool - if config and config.get('field_key'): - self.field_key = config['field_key'] - else: - self.field_key = ( - "%s-%s" % (record._name, field_name)).replace('.', '') - if config and config.get('base_location'): - self.base_location = config['base_location'] - else: - self.base_location = self.pool.get('ir.config_parameter').\ - get_param(cr, uid, 'binary.location') - if not self.base_location: - raise orm.except_orm( - _('Configuration Error'), - _('The "binary.location" is empty, please fill it in' - 'Configuration > Parameters > System Parameters')) - self.location = (self.base_location, self.field_key) + self.field_name = field_name + self.model_name = model_name + self.config = config + + +class FileSystemStorage(Storage): + + def _full_path(self, cr, uid, fname): + return os.path.join( + self.config['base_path'], + self.cr.dbname, + '%s-%s' % (self.model_name, self.field_name), + fname) + + # Code extracted from Odoo V8 in ir_attachment.py + # Copyright (C) 2004-2014 OPENERP SA + # Licence AGPL V3 + def _get_path(self, cr, uid, bin_data): + sha = hashlib.sha1(bin_data).hexdigest() + # scatter files across 256 dirs + # we use '/' in the db (even on windows) + fname = sha[:2] + '/' + sha + full_path = self._full_path(cr, uid, fname) + dirname = os.path.dirname(full_path) + if not os.path.isdir(dirname): + os.makedirs(dirname) + return fname, full_path + + def _file_read(self, cr, uid, fname, bin_size=False): + full_path = self._full_path(cr, uid, fname) + r = '' + try: + if bin_size: + r = os.path.getsize(full_path) + else: + r = open(full_path,'rb').read().encode('base64') + except IOError: + _logger.error("_read_file reading %s",full_path) + return r + + def _file_write(self, cr, uid, value): + bin_value = value.decode('base64') + fname, full_path = self._get_path(cr, uid, bin_value) + if not os.path.exists(full_path): + try: + with open(full_path, 'wb') as fp: + fp.write(bin_value) + except IOError: + _logger.error("_file_write writing %s", full_path) + return fname + + def _file_delete(self, cr, uid, fname): + #TODO FIXME + count = 1 #self.search(cr, 1, [('store_fname','=',fname)], count=True) + full_path = self._full_path(cr, uid, fname) + if count <= 1 and os.path.exists(full_path): + try: + os.unlink(full_path) + except OSError: + _logger.error("_file_delete could not unlink %s",full_path) + except IOError: + # Harmless and needed for race conditions + _logger.error("_file_delete could not unlink %s",full_path) + # END of extraction def add(self, value): if not value: return {} file_size = sys.getsizeof(value.decode('base64')) - binary_uid = self.pool['ir.attachment'].\ - _file_write(self.cr, self.uid, self.location, value) - _logger.debug('Add binary %s/%s' % (self.field_key, binary_uid)) + _logger.debug('Add binary to model: %s, field: %s' + % (self.model_name, self.field_name)) + binary_uid = self._file_write(self.cr, self.uid, value) return { 'binary_uid': binary_uid, 'file_size': file_size, } def update(self, binary_uid, value): - _logger.debug('Delete binary %s/%s' % (self.field_key, binary_uid)) - self.pool['ir.attachment'].\ - _file_delete(self.cr, self.uid, self.location, binary_uid) + _logger.debug('Delete binary model: %s, field: %s, uid: %s' + % (self.model_name, self.field_name, binary_uid)) + self._file_delete(self.cr, self.uid, binary_uid) if not value: return {} return self.add(value) def get(self, binary_uid): - return self.pool['ir.attachment'].\ - _file_read(self.cr, self.uid, self.location, binary_uid) + return self._file_read(self.cr, self.uid, binary_uid) class BinaryField(fields.function): - def __init__(self, string, get_storage=Storage, config=None, **kwargs): + def __init__(self, string, **kwargs): """Init a BinaryField field :params string: Name of the field :type string: str @@ -99,8 +147,6 @@ def __init__(self, string, get_storage=Storage, config=None, **kwargs): 'multi': False, } new_kwargs.update(kwargs) - self.get_storage = get_storage - self.config = config super(BinaryField, self).__init__(self._fnct_read, **new_kwargs) # No postprocess are needed @@ -118,9 +164,9 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, context=None): if not isinstance(ids, (list, tuple)): ids = [ids] + storage_obj = obj.pool['storage.configuration'] for record in obj.browse(cr, uid, ids, context=context): - storage = self.get_storage(cr, uid, record, field_name, - config=self.config) + storage = storage_obj.get_storage(cr, uid, field_name, record) binary_uid = record['%s_uid' % field_name] if binary_uid: res = storage.update(binary_uid, value) @@ -133,9 +179,9 @@ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): result = {} + storage_obj = obj.pool['storage.configuration'] for record in obj.browse(cr, uid, ids, context=context): - storage = self.get_storage(cr, uid, record, field_name, - config=self.config) + storage = storage_obj.get_storage(cr, uid, field_name, record) binary_uid = record['%s_uid' % field_name] if binary_uid: # Compatibility with existing binary field @@ -260,23 +306,5 @@ def __init__(self, pool, cr): orm.BaseModel.__init__ = __init__ -class IrAttachment(orm.Model): - _inherit = 'ir.attachment' - - def _full_path(self, cr, uid, location, path): - # Hack for passing the field_key in the full path - # For now I prefer to use this hack and to reuse - # the ir.attachment code - # An alternative way will to copy/paste and - # adapt the ir.attachment code - if isinstance(location, tuple): - base_location, field_key = location - path = os.path.join(field_key, path) - else: - base_location = location - return super(IrAttachment, self).\ - _full_path(cr, uid, base_location, path) - - class BinaryFieldInstalled(orm.AbstractModel): _name = 'binary.field.installed' diff --git a/binary_field/ir_model.py b/binary_field/ir_model.py new file mode 100644 index 00000000000..7ed97150277 --- /dev/null +++ b/binary_field/ir_model.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2014 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################### + +from openerp.osv import fields, orm + +class IrModelFields(orm.Model): + _inherit = 'ir.model.fields' + + _columns = { + 'storage_id': fields.many2one( + 'storage.configuration', + 'Custom Storage', + help=("Select a custom storage configuration. " + "If the field is empty the default one will be use") + ), + } diff --git a/binary_field/ir_model_view.xml b/binary_field/ir_model_view.xml new file mode 100644 index 00000000000..b0ae0993758 --- /dev/null +++ b/binary_field/ir_model_view.xml @@ -0,0 +1,26 @@ + + + + + + ir.model + + + + + + + + + + ir.model.fields + + + + + + + + + + diff --git a/binary_field/storage.py b/binary_field/storage.py new file mode 100644 index 00000000000..0ea6aef286c --- /dev/null +++ b/binary_field/storage.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Module for OpenERP +# Copyright (C) 2014 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################### + +from .fields import FileSystemStorage +from openerp.osv import fields, orm + + +class StorageConfiguration(orm.Model): + _name = 'storage.configuration' + _description = 'storage configuration' + + def _get_storage_map_class(self, cr, uid, context=None): + return { + 'filesystem' : FileSystemStorage, + } + + def _get_class(self, cr, uid, type, context=None): + map_class = self._get_storage_map_class(cr, uid, context=context) + return map_class[type] + + def _get_config(self, cr, uid, model_name, field_name, context=None): + field_obj = self.pool['ir.model.fields'] + field_id = field_obj.search(cr, uid, [ + ('model', '=', model_name), + ('name', '=', field_name), + ], context=context) + if not field_id: + raise orm.except_orm( + _('Dev Error'), + _('The field %s with do not exist on the model %s') + %(field, model)) + else: + field_id = field_id[0] + field = field_obj.browse(cr, uid, field_id, context=context) + storage_id = field.storage_id.id + if not storage_id: + storage_id = self.search(cr, uid, [ + ('is_default', '=', True), + ], context=context) + if storage_id: + storage_id = storage_id[0] + else: + raise orm.except_orm( + _('User Error'), + _('There is not default storage configuration, ' + 'please add one')) + return self.read(cr, uid, storage_id, self._columns.keys(), + context=context) + + def get_storage(self, cr, uid, field_name, record, context=None): + model_name = record._name + config = self._get_config(cr, uid, record._name, field_name) + storage_class = self._get_class( + cr, uid, config['type'], context=context) + return storage_class(cr, uid, model_name, field_name, record, config) + + def _get_storage_type(self, cr, uid, context=None): + return [('filesystem', 'File System')] + + def __get_storage_type(self, cr, uid, context=None): + return self._get_storage_type(cr, uid, context=context) + + def _remove_default(self, cr, uid, context=None): + conf_id = self.search(cr, uid, [ + ('is_default', '=', True), + ], context=context) + self.write(cr, uid, conf_id, { + 'is_default': False, + }, context=context) + + def create(self, cr, uid, vals, context=None): + if context is None: + context = {} + if vals.get('is_default'): + self._remove_default(cr, uid, context=context) + return super(StorageConfiguration, self).\ + create(cr, uid, vals, context=context) + + def write(self, cr, uid, ids, vals, context=None): + if context is None: + context = {} + if vals.get('is_default'): + self._remove_default(cr, uid, context=context) + return super(StorageConfiguration, self).\ + write(cr, uid, ids, vals, context=context) + + _columns = { + 'name': fields.char('Name'), + 'type': fields.selection( + __get_storage_type, + 'Type', + help='Type of storage'), + 'base_path': fields.char('Path'), + 'is_default': fields.boolean( + 'Is default', + help=('Tic that box in order to select ' + 'the default storage configuration')), + } diff --git a/binary_field/storage_view.xml b/binary_field/storage_view.xml new file mode 100644 index 00000000000..57f3d6bb23c --- /dev/null +++ b/binary_field/storage_view.xml @@ -0,0 +1,65 @@ + + + + + storage.configuration + + + + + + + + + + storage.configuration + +
+ + + + + +
+ + + storage.configuration + + + + + + + + + Storage Configuration + ir.actions.act_window + storage.configuration + form + tree,form + + [] + {} + + + + + + form + + + + + + + tree + + + + + +
+
From 6c37169352fd32e9fa2a90beaa5a670f2e788a0e Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Tue, 1 Jul 2014 15:02:07 +0200 Subject: [PATCH 21/25] [FIX] fix delete methode --- binary_field/fields.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/binary_field/fields.py b/binary_field/fields.py index 727ceca32b5..7c627bff1d1 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -90,8 +90,10 @@ def _file_write(self, cr, uid, value): return fname def _file_delete(self, cr, uid, fname): - #TODO FIXME - count = 1 #self.search(cr, 1, [('store_fname','=',fname)], count=True) + obj = self.pool[self.model_name] + count = obj.search(cr, 1, [ + ('%s_uid' % self.field_name, '=', fname), + ], count=True) full_path = self._full_path(cr, uid, fname) if count <= 1 and os.path.exists(full_path): try: From fab3550aad2413a84b9f9931939aa76a6d27d1fb Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Tue, 1 Jul 2014 20:54:27 +0200 Subject: [PATCH 22/25] [FIX] Fix init method, original method should be call at the end --- binary_field/fields.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/binary_field/fields.py b/binary_field/fields.py index 7c627bff1d1..5926152d575 100644 --- a/binary_field/fields.py +++ b/binary_field/fields.py @@ -288,8 +288,7 @@ def _refresh_cache(self, obj, cr, uid, ids, field_name, context=None): def __init__(self, pool, cr): - original__init__(self, pool, cr) - if self.pool.get('binary.field.installed'): + if pool.get('binary.field.installed'): additional_field = {} for field_name in self._columns: field = self._columns[field_name] @@ -301,8 +300,8 @@ def __init__(self, pool, cr): fields.integer( '%s File Size' % self._columns[field_name].string), }) - #import pdb; pdb.set_trace() self._columns.update(additional_field) + original__init__(self, pool, cr) orm.BaseModel.__init__ = __init__ From f27c34ec7d178f36ac9c3659b1f162803bb633e8 Mon Sep 17 00:00:00 2001 From: Sebastien Beau Date: Tue, 1 Jul 2014 20:54:53 +0200 Subject: [PATCH 23/25] [FIX] default storage should be in no update state --- binary_field/data.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binary_field/data.xml b/binary_field/data.xml index 226e68857df..8c65526c28e 100644 --- a/binary_field/data.xml +++ b/binary_field/data.xml @@ -1,6 +1,6 @@ - + filesystem Default File System Storage From 84dcd106f0694841d959f23c86931b38e71d215c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU?= Date: Fri, 4 Jul 2014 16:39:16 +0200 Subject: [PATCH 24/25] [FIX] remove useless file added by error --- binary_field/< | 274 ------------------------------------------------- 1 file changed, 274 deletions(-) delete mode 100644 binary_field/< diff --git a/binary_field/< b/binary_field/< deleted file mode 100644 index 96f0f669812..00000000000 --- a/binary_field/< +++ /dev/null @@ -1,274 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################### -# -# Module for OpenERP -# Copyright (C) 2014 Akretion (http://www.akretion.com). -# @author Sébastien BEAU -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################### - -from openerp.osv import fields, orm -from openerp.tools import image_resize_image -from openerp.tools.translate import _ -from openerp import tools -import os -import sys -import logging - -_logger = logging.getLogger(__name__) - - -class Storage(object): - - def __init__(self, cr, uid, record, field_name, config=None): - self.cr = cr - self.uid = uid - self.pool = record._model.pool - if config and config.get('field_key'): - self.field_key = config['field_key'] - else: - self.field_key = ( - "%s-%s" % (record._name, field_name)).replace('.', '') - if config and config.get('base_location'): - self.base_location = config['base_location'] - else: - self.base_location = self.pool.get('ir.config_parameter').\ - get_param(cr, uid, 'binary.location') - if not self.base_location: - raise orm.except_orm( - _('Configuration Error'), - _('The "binary.location" is empty, please fill it in' - 'Configuration > Parameters > System Parameters')) - self.location = (self.base_location, self.field_key) - - def add(self, value): - if not value: - return {} - file_size = sys.getsizeof(value.decode('base64')) - binary_uid = self.pool['ir.attachment'].\ - _file_write(self.cr, self.uid, self.location, value) - _logger.debug('Add binary %s/%s' % (self.field_key, binary_uid)) - return { - 'binary_uid': binary_uid, - 'file_size': file_size, - } - - def update(self, binary_uid, value): - _logger.debug('Delete binary %s/%s' % (self.field_key, binary_uid)) - self.pool['ir.attachment'].\ - _file_delete(self.cr, self.uid, self.location, binary_uid) - if not value: - return {} - return self.add(value) - - def get(self, binary_uid): - return self.pool['ir.attachment'].\ - _file_read(self.cr, self.uid, self.location, binary_uid) - - -class BinaryField(fields.function): - - def __init__(self, string, get_storage=Storage, config=None, **kwargs): - """Init a BinaryField field - :params string: Name of the field - :type string: str - :params get_storage: Storage Class for processing the field - by default use the Storage on filesystem - :type get_storage: :py:class`binary_field.Storage' - :params config: configuration used by the storage class - :type config: what you want it's depend of the Storage class - implementation - """ - new_kwargs = { - 'type': 'binary', - 'string': string, - 'fnct': self._fnct_read, - 'fnct_inv': self._fnct_write, - 'multi': False, - } - new_kwargs.update(kwargs) - self.get_storage = get_storage - self.config = config - super(BinaryField, self).__init__(**new_kwargs) - - # No postprocess are needed - # we already take care of bin_size option in the context - def postprocess(self, cr, uid, obj, field, value=None, context=None): - return value - - def _prepare_binary_meta(self, cr, uid, field_name, res, context=None): - return { - '%s_uid' % field_name: res.get('binary_uid'), - '%s_file_size' % field_name: res.get('file_size'), - } - - def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, - context=None): - if not isinstance(ids, (list, tuple)): - ids = [ids] - for record in obj.browse(cr, uid, ids, context=context): - storage = self.get_storage(cr, uid, record, field_name, - config=self.config) - binary_uid = record['%s_uid' % field_name] - if binary_uid: - res = storage.update(binary_uid, value) - else: - res = storage.add(value) - vals = self._prepare_binary_meta( - cr, uid, field_name, res, context=context) - record.write(vals) - return True - - def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): - result = {} - for record in obj.browse(cr, uid, ids, context=context): - storage = self.get_storage(cr, uid, record, field_name, - config=self.config) - binary_uid = record['%s_uid' % field_name] - if binary_uid: - # Compatibility with existing binary field - if context.get( - 'bin_size_%s' % field_name, context.get('bin_size') - ): - size = record['%s_file_size' % field_name] - result[record.id] = tools.human_size(long(size)) - else: - result[record.id] = storage.get(binary_uid) - else: - result[record.id] = None - return result - - -class ImageField(BinaryField): - - def __init__(self, string, get_storage=Storage, config=None, - resize_based_on=None, height=64, width=64, **kwargs): - """Init a ImageField field - :params string: Name of the field - :type string: str - :params get_storage: Storage Class for processing the field - by default use the Storage on filesystem - :type get_storage: :py:class`binary_field.Storage' - :params config: configuration used by the storage class - :type config: what you want it's depend of the Storage class - implementation - :params resize_based_on: reference field that should be resized - :type resize_based_on: str - :params height: height of the image resized - :type height: integer - :params width: width of the image resized - :type width: integer - """ - super(BinaryField, self).__init__( - string, - get_storage=get_storage, - config=config, - **kwargs) - self.resize_base_on = resize_based_on - - def _fnct_write(self, obj, cr, uid, ids, field_name, value, args, - context=None): - if context is None: - context = {} - related_field_name = obj._columns[field_name].resize_based_on - - # If we write an original image in a field with the option resized - # We have to store the image on the related field and not on the - # resized image field - if related_field_name and not context.get('refresh_image_cache'): - return super(ImageField, self)._fnct_write( - obj, cr, uid, ids, related_field_name, value, args, - context=context) - else: - super(ImageField, self)._fnct_write( - obj, cr, uid, ids, field_name, value, args, context=context) - - for name, field in obj._columns.items(): - if field.resize_based_on == field_name: - field._refresh_cache( - obj, cr, uid, ids, name, context=context) - return True - - def _refresh_cache(self, obj, cr, uid, ids, field_name, context=None): - if context is None: - context = {} - if not isinstance(ids, (list, tuple)): - ids = [ids] - for record_id in ids: - _logger.debug('Refreshing Image Cache from the field %s of object ' - '%s id : %s' % (field_name, obj._name, record_id)) - field = obj._columns[field_name] - record = obj.browse(cr, uid, record_id, context=context) - original_image = record[field.related_field] - if original_image: - size = (field.height, field.width) - resized_image = image_resize_image(original_image, size) - else: - resized_image = None - ctx = context.copy() - ctx['refresh_image_cache'] = True - self._fnct_write(obj, cr, uid, [record_id], field_name, - resized_image, None, context=ctx) - - -fields.BinaryField = BinaryField -fields.ImageField = ImageField - - -original__init__ = orm.BaseModel.__init__ - - -def __init__(self, pool, cr): - original__init__(self, pool, cr) - if self.pool.get('binary.field.installed'): - additional_field = {} - for field_name in self._columns: - field = self._columns[field_name] - if isinstance(field, BinaryField): - additional_field.update({ - '%s_uid' % field_name: - fields.char('%s UID' % self._columns[field_name].string), - '%s_file_size' % field_name: - fields.integer( - '%s File Size' % self._columns[field_name].string), - }) - import pdb; pdb.set_trace() - self._columns.update(additional_field) - - -orm.BaseModel.__init__ = __init__ - - -class IrAttachment(orm.Model): - _inherit = 'ir.attachment' - - def _full_path(self, cr, uid, location, path): - # Hack for passing the field_key in the full path - # For now I prefer to use this hack and to reuse - # the ir.attachment code - # An alternative way will to copy/paste and - # adapt the ir.attachment code - if isinstance(location, tuple): - base_location, field_key = location - path = os.path.join(field_key, path) - else: - base_location = location - return super(IrAttachment, self).\ - _full_path(cr, uid, base_location, path) - - -class BinaryFieldInstalled(orm.AbstractModel): - _name = 'binary.field.installed' From 12b6d95fd3fec3aa997608dc324b776c8f13ba2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU?= Date: Tue, 8 Jul 2014 13:06:11 +0200 Subject: [PATCH 25/25] [IMP] add the concept of external server for serving the image --- binary_field/.fields.py.swp | Bin 0 -> 28672 bytes binary_field/__openerp__.py | 5 +- binary_field/fields.py | 61 +++++++++++++++----- binary_field/static/src/js/.widget.js.swp | Bin 0 -> 12288 bytes binary_field/static/src/js/widget.js | 65 ++++++++++++++++++++++ binary_field/storage.py | 11 ++++ binary_field/storage_view.xml | 2 + 7 files changed, 130 insertions(+), 14 deletions(-) create mode 100644 binary_field/.fields.py.swp create mode 100644 binary_field/static/src/js/.widget.js.swp create mode 100644 binary_field/static/src/js/widget.js diff --git a/binary_field/.fields.py.swp b/binary_field/.fields.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..a49d701b522a9242360c9fe6a6a07e827df61c3f GIT binary patch literal 28672 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 img').remove(); + this.$el.prepend($img); + $img.load(function() { + if (! self.options.size) + return; + $img.css("max-width", "" + self.options.size[0] + "px"); + $img.css("max-height", "" + self.options.size[1] + "px"); + $img.css("margin-left", "" + + (self.options.size[0] - $img.width()) / 2 + + "px"); + $img.css("margin-top", "" + + (self.options.size[1] - $img.height()) / 2 + + "px"); + }); + $img.on('error', function() { + $img.attr('src', self.placeholder); + instance.webclient.notification.warn( + _t("Image"), + _t("Could not display the selected image.")); + }); + } else { + return this._super() + }; + }}); + + + instance.web_kanban.KanbanRecord.include({ + kanban_image: function(model, field, id, cache, options) { + console.log(this.record[field].value) + if (this.record[field] + && this.record[field].value + && instance.web.form.is_url(this.record[field].value) + ) { + return this.record[field].value; + } else { + return this._super(); + } + }, + }) + +}; diff --git a/binary_field/storage.py b/binary_field/storage.py index 0ea6aef286c..8c0a7688d4d 100644 --- a/binary_field/storage.py +++ b/binary_field/storage.py @@ -114,4 +114,15 @@ def write(self, cr, uid, ids, vals, context=None): 'Is default', help=('Tic that box in order to select ' 'the default storage configuration')), + 'external_storage_server': fields.boolean( + 'External Storage Server', + help=('Tic that box if you want to server the file with an ' + 'external server. For example, if you choose the storage ' + 'on File system, the binary file can be serve directly with ' + 'nginx or apache...')), + 'base_external_url': fields.char( + 'Base external URL', + help=('When you use an external server for storing the binary ' + 'you have to enter the base of the url where the binary can' + ' be accesible.')), } diff --git a/binary_field/storage_view.xml b/binary_field/storage_view.xml index 57f3d6bb23c..aa4deef81bc 100644 --- a/binary_field/storage_view.xml +++ b/binary_field/storage_view.xml @@ -18,6 +18,8 @@ + +