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
=== added directory 'binary_field'
=== added file 'binary_field/__init__.py'
--- binary_field/__init__.py 1970-01-01 00:00:00 +0000
+++ binary_field/__init__.py 2014-07-04 09:58:26 +0000
@@ -0,0 +1,25 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2013 Akretion (http://www.akretion.com).
6# @author Sébastien BEAU <sebastien.beau@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23from . import fields
24from . import storage
25from . import ir_model
026
=== added file 'binary_field/__openerp__.py'
--- binary_field/__openerp__.py 1970-01-01 00:00:00 +0000
+++ binary_field/__openerp__.py 2014-07-04 09:58:26 +0000
@@ -0,0 +1,80 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2013 Akretion (http://www.akretion.com).
6# @author Sébastien BEAU <sebastien.beau@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23{'name': 'Binary Field',
24 'version': '0.0.1',
25 'author': 'Akretion',
26 'website': 'www.akretion.com',
27 'license': 'AGPL-3',
28 'category': 'Framework',
29 'description': """This module extend the fields class in order to add 3 new
30type of fields.
31- BinaryStore
32- ImageStore
33- ImageRezise
34
35All of this fields will be store on the file system by default and not in the
36database. If you want to store it on an other support (database, S3, ftp,
37SFTP...)
38Then you should create your own 'storage class' and use your custom 'storage
39class' instead
40
41The default Storage class will store the field on the file system and build
42the path like that
43
44 BASE_LOCATION/DB_NAME/MODEL-FIELD/XXX/YYYYY
45
46with
47
48- BASE_LOCATION: the base location configured in ir.config_parameter
49- DB_NAME: your database name
50- MODEL-FIELD: the concatenation of the name of the model with the name of the
51field, for example 'product_product-image'
52- XXX: the first 3 letter of the file name build with their sha1 hash
53- YYYYYY: file name build with their sha1 hash
54
55Here is an example of field declaration
56
57 'binary_test': fields.BinaryField('Test Binary'),
58 'image_test': fields.ImageField('Test Image',
59 config={
60 'field_key': 'StoreMeHere',
61 'base_location': 'file:///testpath',
62 }),
63 'image_test_resize': fields.ImageResizeField(
64 related_field='image_test',
65 string='Test Image small',
66 height=64,
67 width=64,
68 ),
69 """,
70 'depends': [
71 'base',
72 ],
73 'data': [
74 'data.xml',
75 'ir_model_view.xml',
76 'storage_view.xml',
77 ],
78 'installable': True,
79 'application': True,
80}
081
=== added file 'binary_field/data.xml'
--- binary_field/data.xml 1970-01-01 00:00:00 +0000
+++ binary_field/data.xml 2014-07-04 09:58:26 +0000
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<openerp>
3 <data noupdate="1">
4 <record id="default_filesystem_storage" model="storage.configuration">
5 <field name="type">filesystem</field>
6 <field name="name">Default File System Storage</field>
7 <field name="base_path">openerp/filestore/</field>
8 <field name="is_default">1</field>
9 </record>
10 </data>
11</openerp>
012
=== added file 'binary_field/fields.py'
--- binary_field/fields.py 1970-01-01 00:00:00 +0000
+++ binary_field/fields.py 2014-07-04 09:58:26 +0000
@@ -0,0 +1,311 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2014 Akretion (http://www.akretion.com).
6# @author Sébastien BEAU <sebastien.beau@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23import hashlib
24from openerp.osv import fields, orm
25from openerp.tools import image_resize_image
26from openerp.tools.translate import _
27from openerp import tools
28import os
29import sys
30import logging
31
32_logger = logging.getLogger(__name__)
33
34
35class Storage(object):
36
37 def __init__(self, cr, uid, model_name, field_name, record, config):
38 self.cr = cr
39 self.uid = uid
40 self.pool = record._model.pool
41 self.field_name = field_name
42 self.model_name = model_name
43 self.config = config
44
45
46class FileSystemStorage(Storage):
47
48 def _full_path(self, cr, uid, fname):
49 return os.path.join(
50 self.config['base_path'],
51 self.cr.dbname,
52 '%s-%s' % (self.model_name, self.field_name),
53 fname)
54
55 # Code extracted from Odoo V8 in ir_attachment.py
56 # Copyright (C) 2004-2014 OPENERP SA
57 # Licence AGPL V3
58 def _get_path(self, cr, uid, bin_data):
59 sha = hashlib.sha1(bin_data).hexdigest()
60 # scatter files across 256 dirs
61 # we use '/' in the db (even on windows)
62 fname = sha[:2] + '/' + sha
63 full_path = self._full_path(cr, uid, fname)
64 dirname = os.path.dirname(full_path)
65 if not os.path.isdir(dirname):
66 os.makedirs(dirname)
67 return fname, full_path
68
69 def _file_read(self, cr, uid, fname, bin_size=False):
70 full_path = self._full_path(cr, uid, fname)
71 r = ''
72 try:
73 if bin_size:
74 r = os.path.getsize(full_path)
75 else:
76 r = open(full_path,'rb').read().encode('base64')
77 except IOError:
78 _logger.error("_read_file reading %s",full_path)
79 return r
80
81 def _file_write(self, cr, uid, value):
82 bin_value = value.decode('base64')
83 fname, full_path = self._get_path(cr, uid, bin_value)
84 if not os.path.exists(full_path):
85 try:
86 with open(full_path, 'wb') as fp:
87 fp.write(bin_value)
88 except IOError:
89 _logger.error("_file_write writing %s", full_path)
90 return fname
91
92 def _file_delete(self, cr, uid, fname):
93 obj = self.pool[self.model_name]
94 count = obj.search(cr, 1, [
95 ('%s_uid' % self.field_name, '=', fname),
96 ], count=True)
97 full_path = self._full_path(cr, uid, fname)
98 if count <= 1 and os.path.exists(full_path):
99 try:
100 os.unlink(full_path)
101 except OSError:
102 _logger.error("_file_delete could not unlink %s",full_path)
103 except IOError:
104 # Harmless and needed for race conditions
105 _logger.error("_file_delete could not unlink %s",full_path)
106 # END of extraction
107
108 def add(self, value):
109 if not value:
110 return {}
111 file_size = sys.getsizeof(value.decode('base64'))
112 _logger.debug('Add binary to model: %s, field: %s'
113 % (self.model_name, self.field_name))
114 binary_uid = self._file_write(self.cr, self.uid, value)
115 return {
116 'binary_uid': binary_uid,
117 'file_size': file_size,
118 }
119
120 def update(self, binary_uid, value):
121 _logger.debug('Delete binary model: %s, field: %s, uid: %s'
122 % (self.model_name, self.field_name, binary_uid))
123 self._file_delete(self.cr, self.uid, binary_uid)
124 if not value:
125 return {}
126 return self.add(value)
127
128 def get(self, binary_uid):
129 return self._file_read(self.cr, self.uid, binary_uid)
130
131
132class BinaryField(fields.function):
133
134 def __init__(self, string, **kwargs):
135 """Init a BinaryField field
136 :params string: Name of the field
137 :type string: str
138 :params get_storage: Storage Class for processing the field
139 by default use the Storage on filesystem
140 :type get_storage: :py:class`binary_field.Storage'
141 :params config: configuration used by the storage class
142 :type config: what you want it's depend of the Storage class
143 implementation
144 """
145 new_kwargs = {
146 'type': 'binary',
147 'string': string,
148 'fnct_inv': self._fnct_write,
149 'multi': False,
150 }
151 new_kwargs.update(kwargs)
152 super(BinaryField, self).__init__(self._fnct_read, **new_kwargs)
153
154 # No postprocess are needed
155 # we already take care of bin_size option in the context
156 def postprocess(self, cr, uid, obj, field, value=None, context=None):
157 return value
158
159 def _prepare_binary_meta(self, cr, uid, field_name, res, context=None):
160 return {
161 '%s_uid' % field_name: res.get('binary_uid'),
162 '%s_file_size' % field_name: res.get('file_size'),
163 }
164
165 def _fnct_write(self, obj, cr, uid, ids, field_name, value, args,
166 context=None):
167 if not isinstance(ids, (list, tuple)):
168 ids = [ids]
169 storage_obj = obj.pool['storage.configuration']
170 for record in obj.browse(cr, uid, ids, context=context):
171 storage = storage_obj.get_storage(cr, uid, field_name, record)
172 binary_uid = record['%s_uid' % field_name]
173 if binary_uid:
174 res = storage.update(binary_uid, value)
175 else:
176 res = storage.add(value)
177 vals = self._prepare_binary_meta(
178 cr, uid, field_name, res, context=context)
179 record.write(vals)
180 return True
181
182 def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None):
183 result = {}
184 storage_obj = obj.pool['storage.configuration']
185 for record in obj.browse(cr, uid, ids, context=context):
186 storage = storage_obj.get_storage(cr, uid, field_name, record)
187 binary_uid = record['%s_uid' % field_name]
188 if binary_uid:
189 # Compatibility with existing binary field
190 if context.get(
191 'bin_size_%s' % field_name, context.get('bin_size')
192 ):
193 size = record['%s_file_size' % field_name]
194 result[record.id] = tools.human_size(long(size))
195 else:
196 result[record.id] = storage.get(binary_uid)
197 else:
198 result[record.id] = None
199 return result
200
201
202class ImageField(BinaryField):
203
204 def __init__(self, string, get_storage=Storage, config=None,
205 resize_based_on=None, height=64, width=64, **kwargs):
206 """Init a ImageField field
207 :params string: Name of the field
208 :type string: str
209 :params get_storage: Storage Class for processing the field
210 by default use the Storage on filesystem
211 :type get_storage: :py:class`binary_field.Storage'
212 :params config: configuration used by the storage class
213 :type config: what you want it's depend of the Storage class
214 implementation
215 :params resize_based_on: reference field that should be resized
216 :type resize_based_on: str
217 :params height: height of the image resized
218 :type height: integer
219 :params width: width of the image resized
220 :type width: integer
221 """
222 super(ImageField, self).__init__(
223 string,
224 get_storage=get_storage,
225 config=config,
226 **kwargs)
227 self.resize_based_on = resize_based_on
228 self.height = height
229 self.width = width
230
231 def _fnct_write(self, obj, cr, uid, ids, field_name, value, args,
232 context=None):
233 if context is None:
234 context = {}
235 related_field_name = obj._columns[field_name].resize_based_on
236
237 # If we write an original image in a field with the option resized
238 # We have to store the image on the related field and not on the
239 # resized image field
240 if related_field_name and not context.get('refresh_image_cache'):
241 return self._fnct_write(
242 obj, cr, uid, ids, related_field_name, value, args,
243 context=context)
244 else:
245 super(ImageField, self)._fnct_write(
246 obj, cr, uid, ids, field_name, value, args, context=context)
247
248 for name, field in obj._columns.items():
249 if isinstance(field, ImageField)\
250 and field.resize_based_on == field_name:
251 field._refresh_cache(
252 obj, cr, uid, ids, name, context=context)
253 return True
254
255 def _refresh_cache(self, obj, cr, uid, ids, field_name, context=None):
256 """Refresh the cache of the small image
257 :params ids: list of object id to refresh
258 :type ids: list
259 :params field_name: Name of the field to refresh the cache
260 :type field_name: str
261 """
262 if context is None:
263 context = {}
264 if not isinstance(ids, (list, tuple)):
265 ids = [ids]
266 for record_id in ids:
267 _logger.debug('Refreshing Image Cache from the field %s of object '
268 '%s id : %s' % (field_name, obj._name, record_id))
269 field = obj._columns[field_name]
270 record = obj.browse(cr, uid, record_id, context=context)
271 original_image = record[field.resize_based_on]
272 if original_image:
273 size = (field.height, field.width)
274 resized_image = image_resize_image(original_image, size)
275 else:
276 resized_image = None
277 ctx = context.copy()
278 ctx['refresh_image_cache'] = True
279 self._fnct_write(obj, cr, uid, [record_id], field_name,
280 resized_image, None, context=ctx)
281
282
283fields.BinaryField = BinaryField
284fields.ImageField = ImageField
285
286
287original__init__ = orm.BaseModel.__init__
288
289
290def __init__(self, pool, cr):
291 if pool.get('binary.field.installed'):
292 additional_field = {}
293 for field_name in self._columns:
294 field = self._columns[field_name]
295 if isinstance(field, BinaryField):
296 additional_field.update({
297 '%s_uid' % field_name:
298 fields.char('%s UID' % self._columns[field_name].string),
299 '%s_file_size' % field_name:
300 fields.integer(
301 '%s File Size' % self._columns[field_name].string),
302 })
303 self._columns.update(additional_field)
304 original__init__(self, pool, cr)
305
306
307orm.BaseModel.__init__ = __init__
308
309
310class BinaryFieldInstalled(orm.AbstractModel):
311 _name = 'binary.field.installed'
0312
=== added file 'binary_field/ir_model.py'
--- binary_field/ir_model.py 1970-01-01 00:00:00 +0000
+++ binary_field/ir_model.py 2014-07-04 09:58:26 +0000
@@ -0,0 +1,35 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2014 Akretion (http://www.akretion.com).
6# @author Sébastien BEAU <sebastien.beau@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23from openerp.osv import fields, orm
24
25class IrModelFields(orm.Model):
26 _inherit = 'ir.model.fields'
27
28 _columns = {
29 'storage_id': fields.many2one(
30 'storage.configuration',
31 'Custom Storage',
32 help=("Select a custom storage configuration. "
33 "If the field is empty the default one will be use")
34 ),
35 }
036
=== added file 'binary_field/ir_model_view.xml'
--- binary_field/ir_model_view.xml 1970-01-01 00:00:00 +0000
+++ binary_field/ir_model_view.xml 2014-07-04 09:58:26 +0000
@@ -0,0 +1,26 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<openerp>
3 <data>
4
5 <record id="view_model_form" model="ir.ui.view">
6 <field name="model">ir.model</field>
7 <field name="inherit_id" ref="base.view_model_form" />
8 <field name="arch" type="xml">
9 <field name="on_delete" position="after">
10 <field name="storage_id"/>
11 </field>
12 </field>
13 </record>
14
15 <record id="view_model_fields_form" model="ir.ui.view">
16 <field name="model">ir.model.fields</field>
17 <field name="inherit_id" ref="base.view_model_fields_form" />
18 <field name="arch" type="xml">
19 <field name="on_delete" position="after">
20 <field name="storage_id"/>
21 </field>
22 </field>
23 </record>
24
25 </data>
26</openerp>
027
=== added file 'binary_field/storage.py'
--- binary_field/storage.py 1970-01-01 00:00:00 +0000
+++ binary_field/storage.py 2014-07-04 09:58:26 +0000
@@ -0,0 +1,117 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2014 Akretion (http://www.akretion.com).
6# @author Sébastien BEAU <sebastien.beau@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23from .fields import FileSystemStorage
24from openerp.osv import fields, orm
25
26
27class StorageConfiguration(orm.Model):
28 _name = 'storage.configuration'
29 _description = 'storage configuration'
30
31 def _get_storage_map_class(self, cr, uid, context=None):
32 return {
33 'filesystem' : FileSystemStorage,
34 }
35
36 def _get_class(self, cr, uid, type, context=None):
37 map_class = self._get_storage_map_class(cr, uid, context=context)
38 return map_class[type]
39
40 def _get_config(self, cr, uid, model_name, field_name, context=None):
41 field_obj = self.pool['ir.model.fields']
42 field_id = field_obj.search(cr, uid, [
43 ('model', '=', model_name),
44 ('name', '=', field_name),
45 ], context=context)
46 if not field_id:
47 raise orm.except_orm(
48 _('Dev Error'),
49 _('The field %s with do not exist on the model %s')
50 %(field, model))
51 else:
52 field_id = field_id[0]
53 field = field_obj.browse(cr, uid, field_id, context=context)
54 storage_id = field.storage_id.id
55 if not storage_id:
56 storage_id = self.search(cr, uid, [
57 ('is_default', '=', True),
58 ], context=context)
59 if storage_id:
60 storage_id = storage_id[0]
61 else:
62 raise orm.except_orm(
63 _('User Error'),
64 _('There is not default storage configuration, '
65 'please add one'))
66 return self.read(cr, uid, storage_id, self._columns.keys(),
67 context=context)
68
69 def get_storage(self, cr, uid, field_name, record, context=None):
70 model_name = record._name
71 config = self._get_config(cr, uid, record._name, field_name)
72 storage_class = self._get_class(
73 cr, uid, config['type'], context=context)
74 return storage_class(cr, uid, model_name, field_name, record, config)
75
76 def _get_storage_type(self, cr, uid, context=None):
77 return [('filesystem', 'File System')]
78
79 def __get_storage_type(self, cr, uid, context=None):
80 return self._get_storage_type(cr, uid, context=context)
81
82 def _remove_default(self, cr, uid, context=None):
83 conf_id = self.search(cr, uid, [
84 ('is_default', '=', True),
85 ], context=context)
86 self.write(cr, uid, conf_id, {
87 'is_default': False,
88 }, context=context)
89
90 def create(self, cr, uid, vals, context=None):
91 if context is None:
92 context = {}
93 if vals.get('is_default'):
94 self._remove_default(cr, uid, context=context)
95 return super(StorageConfiguration, self).\
96 create(cr, uid, vals, context=context)
97
98 def write(self, cr, uid, ids, vals, context=None):
99 if context is None:
100 context = {}
101 if vals.get('is_default'):
102 self._remove_default(cr, uid, context=context)
103 return super(StorageConfiguration, self).\
104 write(cr, uid, ids, vals, context=context)
105
106 _columns = {
107 'name': fields.char('Name'),
108 'type': fields.selection(
109 __get_storage_type,
110 'Type',
111 help='Type of storage'),
112 'base_path': fields.char('Path'),
113 'is_default': fields.boolean(
114 'Is default',
115 help=('Tic that box in order to select '
116 'the default storage configuration')),
117 }
0118
=== added file 'binary_field/storage_view.xml'
--- binary_field/storage_view.xml 1970-01-01 00:00:00 +0000
+++ binary_field/storage_view.xml 2014-07-04 09:58:26 +0000
@@ -0,0 +1,65 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<openerp>
3 <data>
4 <record id="view_storage_configuration_tree" model="ir.ui.view">
5 <field name="model">storage.configuration</field>
6 <field name="arch" type="xml">
7 <tree string="Storage Configuration">
8 <field name="name"/>
9 <field name="type"/>
10 </tree>
11 </field>
12 </record>
13
14 <record id="view_storage_configuration_form" model="ir.ui.view">
15 <field name="model">storage.configuration</field>
16 <field name="arch" type="xml">
17 <form string="Storage Configuration">
18 <field name="name"/>
19 <field name="type"/>
20 <field name="base_path"/>
21 </form>
22 </field>
23 </record>
24
25 <record id="view_storage_configuration_search" model="ir.ui.view">
26 <field name="model">storage.configuration</field>
27 <field name="arch" type="xml">
28 <search string="Storage Configuration">
29 <field name="name"/>
30 </search>
31 </field>
32 </record>
33
34 <record model="ir.actions.act_window" id="act_open_storage_configuration_view">
35 <field name="name">Storage Configuration</field>
36 <field name="type">ir.actions.act_window</field>
37 <field name="res_model">storage.configuration</field>
38 <field name="view_type">form</field>
39 <field name="view_mode">tree,form</field>
40 <field name="search_view_id" ref="view_storage_configuration_search"/>
41 <field name="domain">[]</field>
42 <field name="context">{}</field>
43 </record>
44
45 <record model="ir.actions.act_window.view" id="act_open_storage_configuration_view_form">
46 <field name="act_window_id" ref="act_open_storage_configuration_view"/>
47 <field name="sequence" eval="20"/>
48 <field name="view_mode">form</field>
49 <field name="view_id" ref="view_storage_configuration_form"/>
50 </record>
51
52 <record model="ir.actions.act_window.view" id="act_open_storage_configuration_view_tree">
53 <field name="act_window_id" ref="act_open_storage_configuration_view"/>
54 <field name="sequence" eval="10"/>
55 <field name="view_mode">tree</field>
56 <field name="view_id" ref="view_storage_configuration_tree"/>
57 </record>
58
59 <menuitem id="menu_storage_configuration"
60 parent="base.next_id_9"
61 sequence="20"
62 action="act_open_storage_configuration_view"/>
63
64 </data>
65</openerp>
066
=== added directory 'binary_field_example'
=== added file 'binary_field_example/__init__.py'
--- binary_field_example/__init__.py 1970-01-01 00:00:00 +0000
+++ binary_field_example/__init__.py 2014-07-04 09:58:26 +0000
@@ -0,0 +1,24 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2013 Akretion (http://www.akretion.com).
6# @author Sébastien BEAU <sebastien.beau@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23from . import res_company
24
025
=== added file 'binary_field_example/__openerp__.py'
--- binary_field_example/__openerp__.py 1970-01-01 00:00:00 +0000
+++ binary_field_example/__openerp__.py 2014-07-04 09:58:26 +0000
@@ -0,0 +1,44 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2013 Akretion (http://www.akretion.com).
6# @author Sébastien BEAU <sebastien.beau@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23{'name': 'binary field example',
24 'version': '0.0.1',
25 'author': 'Akretion',
26 'website': 'www.akretion.com',
27 'license': 'AGPL-3',
28 'category': 'Generic Modules',
29 'description': """Just an example
30
31 """,
32 'depends': [
33 'binary_field',
34 ],
35 'data': [
36 'res_company_view.xml',
37 ],
38 'installable': True,
39 'application': True,
40}
41
42
43
44
045
=== added file 'binary_field_example/res_company.py'
--- binary_field_example/res_company.py 1970-01-01 00:00:00 +0000
+++ binary_field_example/res_company.py 2014-07-04 09:58:26 +0000
@@ -0,0 +1,38 @@
1# -*- coding: utf-8 -*-
2###############################################################################
3#
4# Module for OpenERP
5# Copyright (C) 2013 Akretion (http://www.akretion.com).
6# @author Sébastien BEAU <sebastien.beau@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21###############################################################################
22
23from openerp.osv import fields, orm
24
25
26class ResCompany(orm.Model):
27 _inherit = 'res.company'
28
29 _columns = {
30 'binary_test': fields.BinaryField('Test Binary'),
31 'image_test': fields.ImageField('Test Image'),
32 'image_test_resize': fields.ImageField(
33 'Test Image small',
34 resize_based_on='image_test',
35 height=64,
36 width=64,
37 ),
38 }
039
=== added file 'binary_field_example/res_company_view.xml'
--- binary_field_example/res_company_view.xml 1970-01-01 00:00:00 +0000
+++ binary_field_example/res_company_view.xml 2014-07-04 09:58:26 +0000
@@ -0,0 +1,18 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<openerp>
3 <data>
4
5 <record id="view_res_company_form" model="ir.ui.view">
6 <field name="model">res.company</field>
7 <field name="inherit_id" ref="base.view_company_form"/>
8 <field name="arch" type="xml">
9 <field name="parent_id" position="after">
10 <field name="binary_test"/>
11 <field name="image_test" widget='image'/>
12 <field name="image_test_resize" widget='image'/>
13 </field>
14 </field>
15 </record>
16
17 </data>
18</openerp>