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