diff --git a/binary_field/.fields.py.swp b/binary_field/.fields.py.swp new file mode 100644 index 00000000000..a49d701b522 Binary files /dev/null and b/binary_field/.fields.py.swp differ diff --git a/binary_field/__init__.py b/binary_field/__init__.py new file mode 100644 index 00000000000..c9d434c4ab0 --- /dev/null +++ b/binary_field/__init__.py @@ -0,0 +1,25 @@ +# -*- 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 fields +from . import storage +from . import ir_model diff --git a/binary_field/__openerp__.py b/binary_field/__openerp__.py new file mode 100644 index 00000000000..6558f2578cb --- /dev/null +++ b/binary_field/__openerp__.py @@ -0,0 +1,83 @@ +# -*- 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 + +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': [ + 'web', + ], + 'data': [ + 'data.xml', + 'ir_model_view.xml', + 'storage_view.xml', + ], + 'js': [ + 'static/src/js/widget.js', + ], + 'installable': True, + 'application': True, +} diff --git a/binary_field/data.xml b/binary_field/data.xml new file mode 100644 index 00000000000..8c65526c28e --- /dev/null +++ b/binary_field/data.xml @@ -0,0 +1,11 @@ + + + + + filesystem + Default File System Storage + openerp/filestore/ + 1 + + + diff --git a/binary_field/fields.py b/binary_field/fields.py new file mode 100644 index 00000000000..37d372cc10d --- /dev/null +++ b/binary_field/fields.py @@ -0,0 +1,346 @@ +# -*- 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 . +# +############################################################################### + +import hashlib +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, model_name, field_name, record, config): + self.cr = cr + self.uid = uid + self.pool = record._model.pool + self.field_name = field_name + self.model_name = model_name + self.config = config + self.external_storage_server = config['external_storage_server'] + + +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): + 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: + 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')) + _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 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): + if not binary_uid: + return None + return self._file_read(self.cr, self.uid, binary_uid) + + def get_url(self, binary_uid): + if not binary_uid: + return None + return os.path.join( + self.config['base_external_url'], + self.cr.dbname, + '%s-%s' % (self.model_name, self.field_name), + binary_uid) + + +class BinaryField(fields.function): + + def __init__(self, string, **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_inv': self._fnct_write, + 'multi': False, + } + new_kwargs.update(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 + 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] + storage_obj = obj.pool['storage.configuration'] + for record in obj.browse(cr, uid, ids, context=context): + 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) + else: + res = storage.add(value) + vals = self._prepare_binary_meta( + cr, uid, field_name, res, context=context) + record.write(vals) + return True + + def _read_binary(self, storage, record, field_name, binary_uid, + context=None): + # Compatibility with existing binary field + if context.get( + 'bin_size_%s' % field_name, context.get('bin_size') + ): + size = record['%s_file_size' % field_name] + return tools.human_size(long(size)) + else: + return storage.get(binary_uid) + + 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 = storage_obj.get_storage(cr, uid, field_name, record) + binary_uid = record['%s_uid' % field_name] + if binary_uid: + result[record.id] = self._read_binary( + storage, record, field_name, binary_uid, context=context) + 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(ImageField, self).__init__( + string, + get_storage=get_storage, + config=config, + **kwargs) + self.resize_based_on = resize_based_on + self.height = height + self.width = width + + 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 _read_binary(self, storage, record, field_name, binary_uid, + context=None): + if not context.get('bin_size_%s' % field_name)\ + and not context.get('bin_base64_%s' % field_name)\ + and storage.external_storage_server: + if context.get('bin_size'): + # To avoid useless call by default for the image + # We never return the bin size but the url + # SO I remove the key in order to avoid the + # automatic conversion in the orm + context.pop('bin_size') + return storage.get_url(binary_uid) + else: + return super(ImageField, self)._read_binary( + storage, record, field_name, binary_uid, context=context) + + 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)): + ids = [ids] + ctx = context.copy() + field = obj._columns[field_name] + ctx['bin_base64_%s' % field.resize_based_on] = True + 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)) + record = obj.browse(cr, uid, record_id, context=ctx) + original_image = record[field.resize_based_on] + if original_image: + size = (field.height, field.width) + resized_image = image_resize_image(original_image, size) + else: + resized_image = None + write_ctx = ctx.copy() + write_ctx['refresh_image_cache'] = True + self._fnct_write(obj, cr, uid, [record_id], field_name, + resized_image, None, context=write_ctx) + + +fields.BinaryField = BinaryField +fields.ImageField = ImageField + + +original__init__ = orm.BaseModel.__init__ + + +def __init__(self, pool, cr): + if 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), + }) + self._columns.update(additional_field) + original__init__(self, pool, cr) + + +orm.BaseModel.__init__ = __init__ + + +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/static/src/js/.widget.js.swp b/binary_field/static/src/js/.widget.js.swp new file mode 100644 index 00000000000..6697101c6db Binary files /dev/null and b/binary_field/static/src/js/.widget.js.swp differ diff --git a/binary_field/static/src/js/widget.js b/binary_field/static/src/js/widget.js new file mode 100644 index 00000000000..dc8a6586dff --- /dev/null +++ b/binary_field/static/src/js/widget.js @@ -0,0 +1,65 @@ + +openerp.binary_field = function (instance) { + var _t = instance.web._t, + _lt = instance.web._lt; + var QWeb = instance.web.qweb; + + instance.web.form.widgets.add('imagefield', + 'instance.web.form.ImageField'); + console.log(instance.web.form.widgets); + + instance.web.form.is_url = function(v) { + var regexp = new RegExp("^((http|https):\/\/)?(www[.])?([a-zA-Z0-9]|-)+([.][a-zA-Z0-9(-|\/|=|?)?]+)+$"); + return regexp.test(v); + }; + + instance.web.form.FieldBinaryImage.include({ + render_value: function() { + var self = this; + var url; + if (this.get('value') && instance.web.form.is_url(this.get('value'))) { + console.log('we got it!!!'); + url = this.get('value'); + var $img = $(QWeb.render("FieldBinaryImage-img", + { widget: this, url: url })); + this.$el.find('> 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 new file mode 100644 index 00000000000..8c0a7688d4d --- /dev/null +++ b/binary_field/storage.py @@ -0,0 +1,128 @@ +# -*- 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')), + '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 new file mode 100644 index 00000000000..aa4deef81bc --- /dev/null +++ b/binary_field/storage_view.xml @@ -0,0 +1,67 @@ + + + + + storage.configuration + + + + + + + + + + storage.configuration + +
+ + + + + + + +
+ + + storage.configuration + + + + + + + + + Storage Configuration + ir.actions.act_window + storage.configuration + form + tree,form + + [] + {} + + + + + + form + + + + + + + tree + + + + + +
+
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..12310a27d9e --- /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.ImageField( + 'Test Image small', + resize_based_on='image_test', + 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 + + + + + + + + + + + +