Merge lp:~akretion-team/server-env-tools/server-env-tools into lp:~server-env-tools-core-editors/server-env-tools/7.0

Proposed by Sébastien BEAU - http://www.akretion.com
Status: Needs review
Proposed branch: lp:~akretion-team/server-env-tools/server-env-tools
Merge into: lp:~server-env-tools-core-editors/server-env-tools/7.0
Diff against target: 855 lines (+794/-0)
12 files modified
binary_field/__init__.py (+25/-0)
binary_field/__openerp__.py (+80/-0)
binary_field/data.xml (+11/-0)
binary_field/fields.py (+311/-0)
binary_field/ir_model.py (+35/-0)
binary_field/ir_model_view.xml (+26/-0)
binary_field/storage.py (+117/-0)
binary_field/storage_view.xml (+65/-0)
binary_field_example/__init__.py (+24/-0)
binary_field_example/__openerp__.py (+44/-0)
binary_field_example/res_company.py (+38/-0)
binary_field_example/res_company_view.xml (+18/-0)
To merge this branch: bzr merge lp:~akretion-team/server-env-tools/server-env-tools
Reviewer Review Type Date Requested Status
Hans Yonathan (community) Needs Fixing
Yannick Vaucher @ Camptocamp pr created on github Needs Resubmitting
Guewen Baconnier @ Camptocamp code review Needs Fixing
Review via email: mp+222291@code.launchpad.net

Description of the change

Here is a proposal too add new fields for image and binary. There is a example module so you can try it. But before merging I propose to remove it.

Here is teh refactor of product image based on it https://code.launchpad.net/~akretion-team/openerp-product-attributes/openerp-product-attributes-product-image

To post a comment you must log in.
Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

I left some comments in the diff.
As discussed, it would be nice to be able to pass a configuration dict or function for the storage in the declaration of the fields.

review: Needs Fixing (code review)
13. By Sébastien BEAU - http://www.akretion.com

[FIX] fix typo, missing context and use interger for file size

14. By Sébastien BEAU - http://www.akretion.com

[IMP] add the params config so you can pass some special config to your Storage Class

15. By Sébastien BEAU - http://www.akretion.com

[IMP] Update example module

Revision history for this message
Georges Racinet (gracinet) wrote :

Hi Sébastien,
could you be a bit more specific in the branch description, what's the intended scope/purpose, that kind of thing ? Maybe as a short, committed doc file ?

Am I right to believe that you did a new field parly to avoid the base64 roundtrips ?

Maybe I overlooked it, but the controllers need serious work too (current OpenERP 7 impl is feeble to say the least.

BTW, I'm interested because of the Postgres Large Object support we published on Bitbucket (https://bitbucket.org/anybox/advanced_attachment/src/37ba6c51432bb106b30c500a00b89ff91c9e5ccc/attachment_large_object/?at=default)

Thanks !

16. By Sébastien BEAU - http://www.akretion.com

[REF] remove useless option filters as this option have been drop with the gtk client

17. By Sébastien BEAU - http://www.akretion.com

[IMP] update description and add doc string

Revision history for this message
Sébastien BEAU - http://www.akretion.com (sebastien.beau) wrote :

Hi Georges,

Sorry I fix the description.
In my case I want to store every binary/image field on the file system and not in postgresql. And this not only for attachement but for every image/binary field (image field on product, on category....) I want this because I want to serve my image directly with nginx without using Postgresql, OpenERP.

You storage on Large Object seem really interesting and as I can pass a custom storage class to my field maybe we can create a new custom class for storing in Large Object and reuse you work.

I add a doc string and update the description, give me your feedback

Thanks

Revision history for this message
Sébastien BEAU - http://www.akretion.com (sebastien.beau) wrote :

Hi
I am asking myself about adding an object "storage.configuration" and a field "storing_configuration_id" on the model "ir.model.fields". The idea will to use a default storing configuration for every field (on file sytem for exemple) but after we will be able to customise on every field from the backend where where you want to store each binary/image fields (database, S3, ftp, SFTP, Filestore....)
Do you like the idea? (not so hard to implement)

Revision history for this message
Lorenzo Battistini (elbati) wrote :
Revision history for this message
Lorenzo Battistini (elbati) wrote :

"I am asking myself about adding an object "storage.configuration" and a field "storing_configuration_id" on the model "ir.model.fields". The idea will to use a default storing configuration for every field (on file sytem for exemple) but after we will be able to customise on every field from the backend where where you want to store each binary/image fields (database, S3, ftp, SFTP, Filestore....)
Do you like the idea?"

I like it :-)

18. By Sébastien BEAU - http://www.akretion.com

[MERGE] merge pep 8 clean up done by Lorenzo Battistini - Agile BG, thanks

19. By Sébastien BEAU - http://www.akretion.com

[REF] refcactor code, merge the class ImageFieldResize into the class ImageField

20. By Sébastien BEAU - http://www.akretion.com

[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

21. By Sébastien BEAU - http://www.akretion.com

[FIX] fix delete methode

22. By Sébastien BEAU - http://www.akretion.com

[FIX] Fix init method, original method should be call at the end

23. By Sébastien BEAU - http://www.akretion.com

[FIX] default storage should be in no update state

24. By Sébastien BEAU - http://www.akretion.com

[FIX] remove useless added file by error

Revision history for this message
Sébastien BEAU - http://www.akretion.com (sebastien.beau) wrote :

Hi I have finish the storage refactor, so please give me your feedback !
Also I have move this MP on github so let's comment here => https://github.com/OCA/server-tools/pull/1

Revision history for this message
Yannick Vaucher @ Camptocamp (yvaucher-c2c) :
review: Needs Resubmitting (pr created on github)
Revision history for this message
Hans Yonathan (hans-yonathan) wrote :

Hi Sebastien,

I tried to use your module, for image field is working fine.
For BinaryField, we can upload, and it can save to our filestore, the problem is when we want to download the file, we cannot open the file anymore.
can you take a look?
I tried your example module, and the behaviour is the same.

review: Needs Fixing

Unmerged revisions

24. By Sébastien BEAU - http://www.akretion.com

[FIX] remove useless added file by error

23. By Sébastien BEAU - http://www.akretion.com

[FIX] default storage should be in no update state

22. By Sébastien BEAU - http://www.akretion.com

[FIX] Fix init method, original method should be call at the end

21. By Sébastien BEAU - http://www.akretion.com

[FIX] fix delete methode

20. By Sébastien BEAU - http://www.akretion.com

[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

19. By Sébastien BEAU - http://www.akretion.com

[REF] refcactor code, merge the class ImageFieldResize into the class ImageField

18. By Sébastien BEAU - http://www.akretion.com

[MERGE] merge pep 8 clean up done by Lorenzo Battistini - Agile BG, thanks

17. By Sébastien BEAU - http://www.akretion.com

[IMP] update description and add doc string

16. By Sébastien BEAU - http://www.akretion.com

[REF] remove useless option filters as this option have been drop with the gtk client

15. By Sébastien BEAU - http://www.akretion.com

[IMP] Update example module

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'binary_field'
2=== added file 'binary_field/__init__.py'
3--- binary_field/__init__.py 1970-01-01 00:00:00 +0000
4+++ binary_field/__init__.py 2014-07-04 09:58:26 +0000
5@@ -0,0 +1,25 @@
6+# -*- coding: utf-8 -*-
7+###############################################################################
8+#
9+# Module for OpenERP
10+# Copyright (C) 2013 Akretion (http://www.akretion.com).
11+# @author Sébastien BEAU <sebastien.beau@akretion.com>
12+#
13+# This program is free software: you can redistribute it and/or modify
14+# it under the terms of the GNU Affero General Public License as
15+# published by the Free Software Foundation, either version 3 of the
16+# License, or (at your option) any later version.
17+#
18+# This program is distributed in the hope that it will be useful,
19+# but WITHOUT ANY WARRANTY; without even the implied warranty of
20+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21+# GNU Affero General Public License for more details.
22+#
23+# You should have received a copy of the GNU Affero General Public License
24+# along with this program. If not, see <http://www.gnu.org/licenses/>.
25+#
26+###############################################################################
27+
28+from . import fields
29+from . import storage
30+from . import ir_model
31
32=== added file 'binary_field/__openerp__.py'
33--- binary_field/__openerp__.py 1970-01-01 00:00:00 +0000
34+++ binary_field/__openerp__.py 2014-07-04 09:58:26 +0000
35@@ -0,0 +1,80 @@
36+# -*- coding: utf-8 -*-
37+###############################################################################
38+#
39+# Module for OpenERP
40+# Copyright (C) 2013 Akretion (http://www.akretion.com).
41+# @author Sébastien BEAU <sebastien.beau@akretion.com>
42+#
43+# This program is free software: you can redistribute it and/or modify
44+# it under the terms of the GNU Affero General Public License as
45+# published by the Free Software Foundation, either version 3 of the
46+# License, or (at your option) any later version.
47+#
48+# This program is distributed in the hope that it will be useful,
49+# but WITHOUT ANY WARRANTY; without even the implied warranty of
50+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
51+# GNU Affero General Public License for more details.
52+#
53+# You should have received a copy of the GNU Affero General Public License
54+# along with this program. If not, see <http://www.gnu.org/licenses/>.
55+#
56+###############################################################################
57+
58+{'name': 'Binary Field',
59+ 'version': '0.0.1',
60+ 'author': 'Akretion',
61+ 'website': 'www.akretion.com',
62+ 'license': 'AGPL-3',
63+ 'category': 'Framework',
64+ 'description': """This module extend the fields class in order to add 3 new
65+type of fields.
66+- BinaryStore
67+- ImageStore
68+- ImageRezise
69+
70+All of this fields will be store on the file system by default and not in the
71+database. If you want to store it on an other support (database, S3, ftp,
72+SFTP...)
73+Then you should create your own 'storage class' and use your custom 'storage
74+class' instead
75+
76+The default Storage class will store the field on the file system and build
77+the path like that
78+
79+ BASE_LOCATION/DB_NAME/MODEL-FIELD/XXX/YYYYY
80+
81+with
82+
83+- BASE_LOCATION: the base location configured in ir.config_parameter
84+- DB_NAME: your database name
85+- MODEL-FIELD: the concatenation of the name of the model with the name of the
86+field, for example 'product_product-image'
87+- XXX: the first 3 letter of the file name build with their sha1 hash
88+- YYYYYY: file name build with their sha1 hash
89+
90+Here is an example of field declaration
91+
92+ 'binary_test': fields.BinaryField('Test Binary'),
93+ 'image_test': fields.ImageField('Test Image',
94+ config={
95+ 'field_key': 'StoreMeHere',
96+ 'base_location': 'file:///testpath',
97+ }),
98+ 'image_test_resize': fields.ImageResizeField(
99+ related_field='image_test',
100+ string='Test Image small',
101+ height=64,
102+ width=64,
103+ ),
104+ """,
105+ 'depends': [
106+ 'base',
107+ ],
108+ 'data': [
109+ 'data.xml',
110+ 'ir_model_view.xml',
111+ 'storage_view.xml',
112+ ],
113+ 'installable': True,
114+ 'application': True,
115+}
116
117=== added file 'binary_field/data.xml'
118--- binary_field/data.xml 1970-01-01 00:00:00 +0000
119+++ binary_field/data.xml 2014-07-04 09:58:26 +0000
120@@ -0,0 +1,11 @@
121+<?xml version="1.0" encoding="UTF-8"?>
122+<openerp>
123+ <data noupdate="1">
124+ <record id="default_filesystem_storage" model="storage.configuration">
125+ <field name="type">filesystem</field>
126+ <field name="name">Default File System Storage</field>
127+ <field name="base_path">openerp/filestore/</field>
128+ <field name="is_default">1</field>
129+ </record>
130+ </data>
131+</openerp>
132
133=== added file 'binary_field/fields.py'
134--- binary_field/fields.py 1970-01-01 00:00:00 +0000
135+++ binary_field/fields.py 2014-07-04 09:58:26 +0000
136@@ -0,0 +1,311 @@
137+# -*- coding: utf-8 -*-
138+###############################################################################
139+#
140+# Module for OpenERP
141+# Copyright (C) 2014 Akretion (http://www.akretion.com).
142+# @author Sébastien BEAU <sebastien.beau@akretion.com>
143+#
144+# This program is free software: you can redistribute it and/or modify
145+# it under the terms of the GNU Affero General Public License as
146+# published by the Free Software Foundation, either version 3 of the
147+# License, or (at your option) any later version.
148+#
149+# This program is distributed in the hope that it will be useful,
150+# but WITHOUT ANY WARRANTY; without even the implied warranty of
151+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
152+# GNU Affero General Public License for more details.
153+#
154+# You should have received a copy of the GNU Affero General Public License
155+# along with this program. If not, see <http://www.gnu.org/licenses/>.
156+#
157+###############################################################################
158+
159+import hashlib
160+from openerp.osv import fields, orm
161+from openerp.tools import image_resize_image
162+from openerp.tools.translate import _
163+from openerp import tools
164+import os
165+import sys
166+import logging
167+
168+_logger = logging.getLogger(__name__)
169+
170+
171+class Storage(object):
172+
173+ def __init__(self, cr, uid, model_name, field_name, record, config):
174+ self.cr = cr
175+ self.uid = uid
176+ self.pool = record._model.pool
177+ self.field_name = field_name
178+ self.model_name = model_name
179+ self.config = config
180+
181+
182+class FileSystemStorage(Storage):
183+
184+ def _full_path(self, cr, uid, fname):
185+ return os.path.join(
186+ self.config['base_path'],
187+ self.cr.dbname,
188+ '%s-%s' % (self.model_name, self.field_name),
189+ fname)
190+
191+ # Code extracted from Odoo V8 in ir_attachment.py
192+ # Copyright (C) 2004-2014 OPENERP SA
193+ # Licence AGPL V3
194+ def _get_path(self, cr, uid, bin_data):
195+ sha = hashlib.sha1(bin_data).hexdigest()
196+ # scatter files across 256 dirs
197+ # we use '/' in the db (even on windows)
198+ fname = sha[:2] + '/' + sha
199+ full_path = self._full_path(cr, uid, fname)
200+ dirname = os.path.dirname(full_path)
201+ if not os.path.isdir(dirname):
202+ os.makedirs(dirname)
203+ return fname, full_path
204+
205+ def _file_read(self, cr, uid, fname, bin_size=False):
206+ full_path = self._full_path(cr, uid, fname)
207+ r = ''
208+ try:
209+ if bin_size:
210+ r = os.path.getsize(full_path)
211+ else:
212+ r = open(full_path,'rb').read().encode('base64')
213+ except IOError:
214+ _logger.error("_read_file reading %s",full_path)
215+ return r
216+
217+ def _file_write(self, cr, uid, value):
218+ bin_value = value.decode('base64')
219+ fname, full_path = self._get_path(cr, uid, bin_value)
220+ if not os.path.exists(full_path):
221+ try:
222+ with open(full_path, 'wb') as fp:
223+ fp.write(bin_value)
224+ except IOError:
225+ _logger.error("_file_write writing %s", full_path)
226+ return fname
227+
228+ def _file_delete(self, cr, uid, fname):
229+ obj = self.pool[self.model_name]
230+ count = obj.search(cr, 1, [
231+ ('%s_uid' % self.field_name, '=', fname),
232+ ], count=True)
233+ full_path = self._full_path(cr, uid, fname)
234+ if count <= 1 and os.path.exists(full_path):
235+ try:
236+ os.unlink(full_path)
237+ except OSError:
238+ _logger.error("_file_delete could not unlink %s",full_path)
239+ except IOError:
240+ # Harmless and needed for race conditions
241+ _logger.error("_file_delete could not unlink %s",full_path)
242+ # END of extraction
243+
244+ def add(self, value):
245+ if not value:
246+ return {}
247+ file_size = sys.getsizeof(value.decode('base64'))
248+ _logger.debug('Add binary to model: %s, field: %s'
249+ % (self.model_name, self.field_name))
250+ binary_uid = self._file_write(self.cr, self.uid, value)
251+ return {
252+ 'binary_uid': binary_uid,
253+ 'file_size': file_size,
254+ }
255+
256+ def update(self, binary_uid, value):
257+ _logger.debug('Delete binary model: %s, field: %s, uid: %s'
258+ % (self.model_name, self.field_name, binary_uid))
259+ self._file_delete(self.cr, self.uid, binary_uid)
260+ if not value:
261+ return {}
262+ return self.add(value)
263+
264+ def get(self, binary_uid):
265+ return self._file_read(self.cr, self.uid, binary_uid)
266+
267+
268+class BinaryField(fields.function):
269+
270+ def __init__(self, string, **kwargs):
271+ """Init a BinaryField field
272+ :params string: Name of the field
273+ :type string: str
274+ :params get_storage: Storage Class for processing the field
275+ by default use the Storage on filesystem
276+ :type get_storage: :py:class`binary_field.Storage'
277+ :params config: configuration used by the storage class
278+ :type config: what you want it's depend of the Storage class
279+ implementation
280+ """
281+ new_kwargs = {
282+ 'type': 'binary',
283+ 'string': string,
284+ 'fnct_inv': self._fnct_write,
285+ 'multi': False,
286+ }
287+ new_kwargs.update(kwargs)
288+ super(BinaryField, self).__init__(self._fnct_read, **new_kwargs)
289+
290+ # No postprocess are needed
291+ # we already take care of bin_size option in the context
292+ def postprocess(self, cr, uid, obj, field, value=None, context=None):
293+ return value
294+
295+ def _prepare_binary_meta(self, cr, uid, field_name, res, context=None):
296+ return {
297+ '%s_uid' % field_name: res.get('binary_uid'),
298+ '%s_file_size' % field_name: res.get('file_size'),
299+ }
300+
301+ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args,
302+ context=None):
303+ if not isinstance(ids, (list, tuple)):
304+ ids = [ids]
305+ storage_obj = obj.pool['storage.configuration']
306+ for record in obj.browse(cr, uid, ids, context=context):
307+ storage = storage_obj.get_storage(cr, uid, field_name, record)
308+ binary_uid = record['%s_uid' % field_name]
309+ if binary_uid:
310+ res = storage.update(binary_uid, value)
311+ else:
312+ res = storage.add(value)
313+ vals = self._prepare_binary_meta(
314+ cr, uid, field_name, res, context=context)
315+ record.write(vals)
316+ return True
317+
318+ def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
319+ result = {}
320+ storage_obj = obj.pool['storage.configuration']
321+ for record in obj.browse(cr, uid, ids, context=context):
322+ storage = storage_obj.get_storage(cr, uid, field_name, record)
323+ binary_uid = record['%s_uid' % field_name]
324+ if binary_uid:
325+ # Compatibility with existing binary field
326+ if context.get(
327+ 'bin_size_%s' % field_name, context.get('bin_size')
328+ ):
329+ size = record['%s_file_size' % field_name]
330+ result[record.id] = tools.human_size(long(size))
331+ else:
332+ result[record.id] = storage.get(binary_uid)
333+ else:
334+ result[record.id] = None
335+ return result
336+
337+
338+class ImageField(BinaryField):
339+
340+ def __init__(self, string, get_storage=Storage, config=None,
341+ resize_based_on=None, height=64, width=64, **kwargs):
342+ """Init a ImageField field
343+ :params string: Name of the field
344+ :type string: str
345+ :params get_storage: Storage Class for processing the field
346+ by default use the Storage on filesystem
347+ :type get_storage: :py:class`binary_field.Storage'
348+ :params config: configuration used by the storage class
349+ :type config: what you want it's depend of the Storage class
350+ implementation
351+ :params resize_based_on: reference field that should be resized
352+ :type resize_based_on: str
353+ :params height: height of the image resized
354+ :type height: integer
355+ :params width: width of the image resized
356+ :type width: integer
357+ """
358+ super(ImageField, self).__init__(
359+ string,
360+ get_storage=get_storage,
361+ config=config,
362+ **kwargs)
363+ self.resize_based_on = resize_based_on
364+ self.height = height
365+ self.width = width
366+
367+ def _fnct_write(self, obj, cr, uid, ids, field_name, value, args,
368+ context=None):
369+ if context is None:
370+ context = {}
371+ related_field_name = obj._columns[field_name].resize_based_on
372+
373+ # If we write an original image in a field with the option resized
374+ # We have to store the image on the related field and not on the
375+ # resized image field
376+ if related_field_name and not context.get('refresh_image_cache'):
377+ return self._fnct_write(
378+ obj, cr, uid, ids, related_field_name, value, args,
379+ context=context)
380+ else:
381+ super(ImageField, self)._fnct_write(
382+ obj, cr, uid, ids, field_name, value, args, context=context)
383+
384+ for name, field in obj._columns.items():
385+ if isinstance(field, ImageField)\
386+ and field.resize_based_on == field_name:
387+ field._refresh_cache(
388+ obj, cr, uid, ids, name, context=context)
389+ return True
390+
391+ def _refresh_cache(self, obj, cr, uid, ids, field_name, context=None):
392+ """Refresh the cache of the small image
393+ :params ids: list of object id to refresh
394+ :type ids: list
395+ :params field_name: Name of the field to refresh the cache
396+ :type field_name: str
397+ """
398+ if context is None:
399+ context = {}
400+ if not isinstance(ids, (list, tuple)):
401+ ids = [ids]
402+ for record_id in ids:
403+ _logger.debug('Refreshing Image Cache from the field %s of object '
404+ '%s id : %s' % (field_name, obj._name, record_id))
405+ field = obj._columns[field_name]
406+ record = obj.browse(cr, uid, record_id, context=context)
407+ original_image = record[field.resize_based_on]
408+ if original_image:
409+ size = (field.height, field.width)
410+ resized_image = image_resize_image(original_image, size)
411+ else:
412+ resized_image = None
413+ ctx = context.copy()
414+ ctx['refresh_image_cache'] = True
415+ self._fnct_write(obj, cr, uid, [record_id], field_name,
416+ resized_image, None, context=ctx)
417+
418+
419+fields.BinaryField = BinaryField
420+fields.ImageField = ImageField
421+
422+
423+original__init__ = orm.BaseModel.__init__
424+
425+
426+def __init__(self, pool, cr):
427+ if pool.get('binary.field.installed'):
428+ additional_field = {}
429+ for field_name in self._columns:
430+ field = self._columns[field_name]
431+ if isinstance(field, BinaryField):
432+ additional_field.update({
433+ '%s_uid' % field_name:
434+ fields.char('%s UID' % self._columns[field_name].string),
435+ '%s_file_size' % field_name:
436+ fields.integer(
437+ '%s File Size' % self._columns[field_name].string),
438+ })
439+ self._columns.update(additional_field)
440+ original__init__(self, pool, cr)
441+
442+
443+orm.BaseModel.__init__ = __init__
444+
445+
446+class BinaryFieldInstalled(orm.AbstractModel):
447+ _name = 'binary.field.installed'
448
449=== added file 'binary_field/ir_model.py'
450--- binary_field/ir_model.py 1970-01-01 00:00:00 +0000
451+++ binary_field/ir_model.py 2014-07-04 09:58:26 +0000
452@@ -0,0 +1,35 @@
453+# -*- coding: utf-8 -*-
454+###############################################################################
455+#
456+# Module for OpenERP
457+# Copyright (C) 2014 Akretion (http://www.akretion.com).
458+# @author Sébastien BEAU <sebastien.beau@akretion.com>
459+#
460+# This program is free software: you can redistribute it and/or modify
461+# it under the terms of the GNU Affero General Public License as
462+# published by the Free Software Foundation, either version 3 of the
463+# License, or (at your option) any later version.
464+#
465+# This program is distributed in the hope that it will be useful,
466+# but WITHOUT ANY WARRANTY; without even the implied warranty of
467+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
468+# GNU Affero General Public License for more details.
469+#
470+# You should have received a copy of the GNU Affero General Public License
471+# along with this program. If not, see <http://www.gnu.org/licenses/>.
472+#
473+###############################################################################
474+
475+from openerp.osv import fields, orm
476+
477+class IrModelFields(orm.Model):
478+ _inherit = 'ir.model.fields'
479+
480+ _columns = {
481+ 'storage_id': fields.many2one(
482+ 'storage.configuration',
483+ 'Custom Storage',
484+ help=("Select a custom storage configuration. "
485+ "If the field is empty the default one will be use")
486+ ),
487+ }
488
489=== added file 'binary_field/ir_model_view.xml'
490--- binary_field/ir_model_view.xml 1970-01-01 00:00:00 +0000
491+++ binary_field/ir_model_view.xml 2014-07-04 09:58:26 +0000
492@@ -0,0 +1,26 @@
493+<?xml version="1.0" encoding="UTF-8"?>
494+<openerp>
495+ <data>
496+
497+ <record id="view_model_form" model="ir.ui.view">
498+ <field name="model">ir.model</field>
499+ <field name="inherit_id" ref="base.view_model_form" />
500+ <field name="arch" type="xml">
501+ <field name="on_delete" position="after">
502+ <field name="storage_id"/>
503+ </field>
504+ </field>
505+ </record>
506+
507+ <record id="view_model_fields_form" model="ir.ui.view">
508+ <field name="model">ir.model.fields</field>
509+ <field name="inherit_id" ref="base.view_model_fields_form" />
510+ <field name="arch" type="xml">
511+ <field name="on_delete" position="after">
512+ <field name="storage_id"/>
513+ </field>
514+ </field>
515+ </record>
516+
517+ </data>
518+</openerp>
519
520=== added file 'binary_field/storage.py'
521--- binary_field/storage.py 1970-01-01 00:00:00 +0000
522+++ binary_field/storage.py 2014-07-04 09:58:26 +0000
523@@ -0,0 +1,117 @@
524+# -*- coding: utf-8 -*-
525+###############################################################################
526+#
527+# Module for OpenERP
528+# Copyright (C) 2014 Akretion (http://www.akretion.com).
529+# @author Sébastien BEAU <sebastien.beau@akretion.com>
530+#
531+# This program is free software: you can redistribute it and/or modify
532+# it under the terms of the GNU Affero General Public License as
533+# published by the Free Software Foundation, either version 3 of the
534+# License, or (at your option) any later version.
535+#
536+# This program is distributed in the hope that it will be useful,
537+# but WITHOUT ANY WARRANTY; without even the implied warranty of
538+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
539+# GNU Affero General Public License for more details.
540+#
541+# You should have received a copy of the GNU Affero General Public License
542+# along with this program. If not, see <http://www.gnu.org/licenses/>.
543+#
544+###############################################################################
545+
546+from .fields import FileSystemStorage
547+from openerp.osv import fields, orm
548+
549+
550+class StorageConfiguration(orm.Model):
551+ _name = 'storage.configuration'
552+ _description = 'storage configuration'
553+
554+ def _get_storage_map_class(self, cr, uid, context=None):
555+ return {
556+ 'filesystem' : FileSystemStorage,
557+ }
558+
559+ def _get_class(self, cr, uid, type, context=None):
560+ map_class = self._get_storage_map_class(cr, uid, context=context)
561+ return map_class[type]
562+
563+ def _get_config(self, cr, uid, model_name, field_name, context=None):
564+ field_obj = self.pool['ir.model.fields']
565+ field_id = field_obj.search(cr, uid, [
566+ ('model', '=', model_name),
567+ ('name', '=', field_name),
568+ ], context=context)
569+ if not field_id:
570+ raise orm.except_orm(
571+ _('Dev Error'),
572+ _('The field %s with do not exist on the model %s')
573+ %(field, model))
574+ else:
575+ field_id = field_id[0]
576+ field = field_obj.browse(cr, uid, field_id, context=context)
577+ storage_id = field.storage_id.id
578+ if not storage_id:
579+ storage_id = self.search(cr, uid, [
580+ ('is_default', '=', True),
581+ ], context=context)
582+ if storage_id:
583+ storage_id = storage_id[0]
584+ else:
585+ raise orm.except_orm(
586+ _('User Error'),
587+ _('There is not default storage configuration, '
588+ 'please add one'))
589+ return self.read(cr, uid, storage_id, self._columns.keys(),
590+ context=context)
591+
592+ def get_storage(self, cr, uid, field_name, record, context=None):
593+ model_name = record._name
594+ config = self._get_config(cr, uid, record._name, field_name)
595+ storage_class = self._get_class(
596+ cr, uid, config['type'], context=context)
597+ return storage_class(cr, uid, model_name, field_name, record, config)
598+
599+ def _get_storage_type(self, cr, uid, context=None):
600+ return [('filesystem', 'File System')]
601+
602+ def __get_storage_type(self, cr, uid, context=None):
603+ return self._get_storage_type(cr, uid, context=context)
604+
605+ def _remove_default(self, cr, uid, context=None):
606+ conf_id = self.search(cr, uid, [
607+ ('is_default', '=', True),
608+ ], context=context)
609+ self.write(cr, uid, conf_id, {
610+ 'is_default': False,
611+ }, context=context)
612+
613+ def create(self, cr, uid, vals, context=None):
614+ if context is None:
615+ context = {}
616+ if vals.get('is_default'):
617+ self._remove_default(cr, uid, context=context)
618+ return super(StorageConfiguration, self).\
619+ create(cr, uid, vals, context=context)
620+
621+ def write(self, cr, uid, ids, vals, context=None):
622+ if context is None:
623+ context = {}
624+ if vals.get('is_default'):
625+ self._remove_default(cr, uid, context=context)
626+ return super(StorageConfiguration, self).\
627+ write(cr, uid, ids, vals, context=context)
628+
629+ _columns = {
630+ 'name': fields.char('Name'),
631+ 'type': fields.selection(
632+ __get_storage_type,
633+ 'Type',
634+ help='Type of storage'),
635+ 'base_path': fields.char('Path'),
636+ 'is_default': fields.boolean(
637+ 'Is default',
638+ help=('Tic that box in order to select '
639+ 'the default storage configuration')),
640+ }
641
642=== added file 'binary_field/storage_view.xml'
643--- binary_field/storage_view.xml 1970-01-01 00:00:00 +0000
644+++ binary_field/storage_view.xml 2014-07-04 09:58:26 +0000
645@@ -0,0 +1,65 @@
646+<?xml version="1.0" encoding="UTF-8"?>
647+<openerp>
648+ <data>
649+ <record id="view_storage_configuration_tree" model="ir.ui.view">
650+ <field name="model">storage.configuration</field>
651+ <field name="arch" type="xml">
652+ <tree string="Storage Configuration">
653+ <field name="name"/>
654+ <field name="type"/>
655+ </tree>
656+ </field>
657+ </record>
658+
659+ <record id="view_storage_configuration_form" model="ir.ui.view">
660+ <field name="model">storage.configuration</field>
661+ <field name="arch" type="xml">
662+ <form string="Storage Configuration">
663+ <field name="name"/>
664+ <field name="type"/>
665+ <field name="base_path"/>
666+ </form>
667+ </field>
668+ </record>
669+
670+ <record id="view_storage_configuration_search" model="ir.ui.view">
671+ <field name="model">storage.configuration</field>
672+ <field name="arch" type="xml">
673+ <search string="Storage Configuration">
674+ <field name="name"/>
675+ </search>
676+ </field>
677+ </record>
678+
679+ <record model="ir.actions.act_window" id="act_open_storage_configuration_view">
680+ <field name="name">Storage Configuration</field>
681+ <field name="type">ir.actions.act_window</field>
682+ <field name="res_model">storage.configuration</field>
683+ <field name="view_type">form</field>
684+ <field name="view_mode">tree,form</field>
685+ <field name="search_view_id" ref="view_storage_configuration_search"/>
686+ <field name="domain">[]</field>
687+ <field name="context">{}</field>
688+ </record>
689+
690+ <record model="ir.actions.act_window.view" id="act_open_storage_configuration_view_form">
691+ <field name="act_window_id" ref="act_open_storage_configuration_view"/>
692+ <field name="sequence" eval="20"/>
693+ <field name="view_mode">form</field>
694+ <field name="view_id" ref="view_storage_configuration_form"/>
695+ </record>
696+
697+ <record model="ir.actions.act_window.view" id="act_open_storage_configuration_view_tree">
698+ <field name="act_window_id" ref="act_open_storage_configuration_view"/>
699+ <field name="sequence" eval="10"/>
700+ <field name="view_mode">tree</field>
701+ <field name="view_id" ref="view_storage_configuration_tree"/>
702+ </record>
703+
704+ <menuitem id="menu_storage_configuration"
705+ parent="base.next_id_9"
706+ sequence="20"
707+ action="act_open_storage_configuration_view"/>
708+
709+ </data>
710+</openerp>
711
712=== added directory 'binary_field_example'
713=== added file 'binary_field_example/__init__.py'
714--- binary_field_example/__init__.py 1970-01-01 00:00:00 +0000
715+++ binary_field_example/__init__.py 2014-07-04 09:58:26 +0000
716@@ -0,0 +1,24 @@
717+# -*- coding: utf-8 -*-
718+###############################################################################
719+#
720+# Module for OpenERP
721+# Copyright (C) 2013 Akretion (http://www.akretion.com).
722+# @author Sébastien BEAU <sebastien.beau@akretion.com>
723+#
724+# This program is free software: you can redistribute it and/or modify
725+# it under the terms of the GNU Affero General Public License as
726+# published by the Free Software Foundation, either version 3 of the
727+# License, or (at your option) any later version.
728+#
729+# This program is distributed in the hope that it will be useful,
730+# but WITHOUT ANY WARRANTY; without even the implied warranty of
731+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
732+# GNU Affero General Public License for more details.
733+#
734+# You should have received a copy of the GNU Affero General Public License
735+# along with this program. If not, see <http://www.gnu.org/licenses/>.
736+#
737+###############################################################################
738+
739+from . import res_company
740+
741
742=== added file 'binary_field_example/__openerp__.py'
743--- binary_field_example/__openerp__.py 1970-01-01 00:00:00 +0000
744+++ binary_field_example/__openerp__.py 2014-07-04 09:58:26 +0000
745@@ -0,0 +1,44 @@
746+# -*- coding: utf-8 -*-
747+###############################################################################
748+#
749+# Module for OpenERP
750+# Copyright (C) 2013 Akretion (http://www.akretion.com).
751+# @author Sébastien BEAU <sebastien.beau@akretion.com>
752+#
753+# This program is free software: you can redistribute it and/or modify
754+# it under the terms of the GNU Affero General Public License as
755+# published by the Free Software Foundation, either version 3 of the
756+# License, or (at your option) any later version.
757+#
758+# This program is distributed in the hope that it will be useful,
759+# but WITHOUT ANY WARRANTY; without even the implied warranty of
760+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
761+# GNU Affero General Public License for more details.
762+#
763+# You should have received a copy of the GNU Affero General Public License
764+# along with this program. If not, see <http://www.gnu.org/licenses/>.
765+#
766+###############################################################################
767+
768+{'name': 'binary field example',
769+ 'version': '0.0.1',
770+ 'author': 'Akretion',
771+ 'website': 'www.akretion.com',
772+ 'license': 'AGPL-3',
773+ 'category': 'Generic Modules',
774+ 'description': """Just an example
775+
776+ """,
777+ 'depends': [
778+ 'binary_field',
779+ ],
780+ 'data': [
781+ 'res_company_view.xml',
782+ ],
783+ 'installable': True,
784+ 'application': True,
785+}
786+
787+
788+
789+
790
791=== added file 'binary_field_example/res_company.py'
792--- binary_field_example/res_company.py 1970-01-01 00:00:00 +0000
793+++ binary_field_example/res_company.py 2014-07-04 09:58:26 +0000
794@@ -0,0 +1,38 @@
795+# -*- coding: utf-8 -*-
796+###############################################################################
797+#
798+# Module for OpenERP
799+# Copyright (C) 2013 Akretion (http://www.akretion.com).
800+# @author Sébastien BEAU <sebastien.beau@akretion.com>
801+#
802+# This program is free software: you can redistribute it and/or modify
803+# it under the terms of the GNU Affero General Public License as
804+# published by the Free Software Foundation, either version 3 of the
805+# License, or (at your option) any later version.
806+#
807+# This program is distributed in the hope that it will be useful,
808+# but WITHOUT ANY WARRANTY; without even the implied warranty of
809+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
810+# GNU Affero General Public License for more details.
811+#
812+# You should have received a copy of the GNU Affero General Public License
813+# along with this program. If not, see <http://www.gnu.org/licenses/>.
814+#
815+###############################################################################
816+
817+from openerp.osv import fields, orm
818+
819+
820+class ResCompany(orm.Model):
821+ _inherit = 'res.company'
822+
823+ _columns = {
824+ 'binary_test': fields.BinaryField('Test Binary'),
825+ 'image_test': fields.ImageField('Test Image'),
826+ 'image_test_resize': fields.ImageField(
827+ 'Test Image small',
828+ resize_based_on='image_test',
829+ height=64,
830+ width=64,
831+ ),
832+ }
833
834=== added file 'binary_field_example/res_company_view.xml'
835--- binary_field_example/res_company_view.xml 1970-01-01 00:00:00 +0000
836+++ binary_field_example/res_company_view.xml 2014-07-04 09:58:26 +0000
837@@ -0,0 +1,18 @@
838+<?xml version="1.0" encoding="UTF-8"?>
839+<openerp>
840+ <data>
841+
842+ <record id="view_res_company_form" model="ir.ui.view">
843+ <field name="model">res.company</field>
844+ <field name="inherit_id" ref="base.view_company_form"/>
845+ <field name="arch" type="xml">
846+ <field name="parent_id" position="after">
847+ <field name="binary_test"/>
848+ <field name="image_test" widget='image'/>
849+ <field name="image_test_resize" widget='image'/>
850+ </field>
851+ </field>
852+ </record>
853+
854+ </data>
855+</openerp>