From d47800d1b66b5ca3eb7ba841e496ae8f99f5d23f Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sat, 2 Jun 2012 18:25:23 +0200 Subject: [PATCH 1/8] ir.attachment move methods bzr revid: al@openerp.com-20120602162523-0zp9bzd6cqu8qgis --- openerp/addons/base/ir/ir_attachment.py | 104 ++++++++++++------------ 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index 4978378f22a..9f8814430b8 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -26,6 +26,58 @@ from osv.orm import except_orm import tools class ir_attachment(osv.osv): + def _name_get_resname(self, cr, uid, ids, object, method, context): + data = {} + for attachment in self.browse(cr, uid, ids, context=context): + model_object = attachment.res_model + res_id = attachment.res_id + if model_object and res_id: + model_pool = self.pool.get(model_object) + res = model_pool.name_get(cr,uid,[res_id],context) + res_name = res and res[0][1] or False + if res_name: + field = self._columns.get('res_name',False) + if field and len(res_name) > field.size: + res_name = res_name[:field.size-3] + '...' + data[attachment.id] = res_name + else: + data[attachment.id] = False + return data + + _name = 'ir.attachment' + _columns = { + 'name': fields.char('Attachment Name',size=256, required=True), + 'datas': fields.binary('Data'), + 'datas_fname': fields.char('File Name',size=256), + 'description': fields.text('Description'), + 'res_name': fields.function(_name_get_resname, type='char', size=128, + string='Resource Name', store=True), + 'res_model': fields.char('Resource Object',size=64, readonly=True, + help="The database object this attachment will be attached to"), + 'res_id': fields.integer('Resource ID', readonly=True, + help="The record id this is attached to"), + 'url': fields.char('Url', size=512, oldname="link"), + 'type': fields.selection( + [ ('url','URL'), ('binary','Binary'), ], + 'Type', help="Binary File or external URL", required=True, change_default=True), + + 'create_date': fields.datetime('Date Created', readonly=True), + 'create_uid': fields.many2one('res.users', 'Owner', readonly=True), + 'company_id': fields.many2one('res.company', 'Company', change_default=True), + } + + _defaults = { + 'type': 'binary', + 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'ir.attachment', context=c), + } + + def _auto_init(self, cr, context=None): + super(ir_attachment, self)._auto_init(cr, context) + cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_attachment_res_idx',)) + if not cr.fetchone(): + cr.execute('CREATE INDEX ir_attachment_res_idx ON ir_attachment (res_model, res_id)') + cr.commit() + def check(self, cr, uid, ids, mode, context=None, values=None): """Restricts the access to an ir.attachment, according to referred model In the 'document' module, it is overriden to relax this hard rule, since @@ -120,58 +172,6 @@ class ir_attachment(osv.osv): return self.pool.get('ir.actions.act_window').for_xml_id( cr, uid, 'base', 'action_attachment', context=context) - def _name_get_resname(self, cr, uid, ids, object, method, context): - data = {} - for attachment in self.browse(cr, uid, ids, context=context): - model_object = attachment.res_model - res_id = attachment.res_id - if model_object and res_id: - model_pool = self.pool.get(model_object) - res = model_pool.name_get(cr,uid,[res_id],context) - res_name = res and res[0][1] or False - if res_name: - field = self._columns.get('res_name',False) - if field and len(res_name) > field.size: - res_name = res_name[:field.size-3] + '...' - data[attachment.id] = res_name - else: - data[attachment.id] = False - return data - - _name = 'ir.attachment' - _columns = { - 'name': fields.char('Attachment Name',size=256, required=True), - 'datas': fields.binary('Data'), - 'datas_fname': fields.char('File Name',size=256), - 'description': fields.text('Description'), - 'res_name': fields.function(_name_get_resname, type='char', size=128, - string='Resource Name', store=True), - 'res_model': fields.char('Resource Object',size=64, readonly=True, - help="The database object this attachment will be attached to"), - 'res_id': fields.integer('Resource ID', readonly=True, - help="The record id this is attached to"), - 'url': fields.char('Url', size=512, oldname="link"), - 'type': fields.selection( - [ ('url','URL'), ('binary','Binary'), ], - 'Type', help="Binary File or external URL", required=True, change_default=True), - - 'create_date': fields.datetime('Date Created', readonly=True), - 'create_uid': fields.many2one('res.users', 'Owner', readonly=True), - 'company_id': fields.many2one('res.company', 'Company', change_default=True), - } - - _defaults = { - 'type': 'binary', - 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'ir.attachment', context=c), - } - - def _auto_init(self, cr, context=None): - super(ir_attachment, self)._auto_init(cr, context) - cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_attachment_res_idx',)) - if not cr.fetchone(): - cr.execute('CREATE INDEX ir_attachment_res_idx ON ir_attachment (res_model, res_id)') - cr.commit() - ir_attachment() From 30ce97d206a92897df6fe4856fe148b01c587d46 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Thu, 7 Jun 2012 23:35:37 +0200 Subject: [PATCH 2/8] [IMP] ir_attachment external storage bzr revid: al@openerp.com-20120607213537-ajm4dihfkco8wssl --- openerp/addons/base/ir/ir_attachment.py | 117 +++++++++++++++++++++--- 1 file changed, 105 insertions(+), 12 deletions(-) diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index 9f8814430b8..dc3ea5faf87 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -26,6 +26,19 @@ from osv.orm import except_orm import tools class ir_attachment(osv.osv): + """Attachments are used to link binary files or url to any openerp document. + + External attachment storage + --------------------------- + + The 'data' function field (_data_get,data_set) is implemented using + _file_read, _file_write and _file_delete which can be overridden to + implement other storage engines, shuch methods should check for other + location pseudo uri (example: hdfs://hadoppserver) + + The default implementation is the file:dirname location that stores files + on the local filesystem using name based on their sha1 hash + """ def _name_get_resname(self, cr, uid, ids, object, method, context): data = {} for attachment in self.browse(cr, uid, ids, context=context): @@ -44,26 +57,106 @@ class ir_attachment(osv.osv): data[attachment.id] = False return data + # 'data' field implementation + def _full_path(self, cr, uid, location, path) + # location = 'file:filestore' + assert location.startswith('file:'), "Unhandled filestore location %s" % location + location = location[5:] + + # sanitize location name and path + location = re.sub('[.]','',location) + location = path.strip('/\\') + + path = re.sub('[.]','',path) + path = path.strip('/\\') + return os.path.join(tools.config['root_path'], location, cr.dbname, path) + + def _file_read(self, cr, uid, location, fname, bin_size=False) + full_path = self._full_path(cr, uid, location, fname) + r = '' + try: + if bin_size: + r = os.path.filesize(full_path) + else: + r = open(full_path).read().encode('base64') + except IOError: + _logger.error("_read_file reading %s",full_path) + return r + + def _file_write(self, cr, uid, location, value): + bin_value = value.decode('base64') + fname = hashlib.sha1(bin_value).hexdigest() + # scatter files across 1024 dirs + # we use '/' in the db (even on windows) + fname = fname[:3] + '/' + fname + full_path = self._full_path(cr, uid, location, fname) + try: + dirname = os.path.dirname(full_path) + if not os.path.isdir(dirname): + os.makedirs(dirname) + open(full_path,'wb').write(bin_value) + except IOError: + _logger.error("_file_write writing %s",full_path) + return fname + + def _file_delete(self, cr, uid, location, fname, threshold=1): + count = self.search(cr, 1, [('store_fname','=',fname)], count=True) + if count <= threshold: + full_path = self._full_path(cr, uid, location, fname) + try: + os.unlink(full_path) + except IOError: + _logger.error("_file_delete could not unlink %s",full_path) + + def _data_get(self, cr, uid, ids, name, arg, context=None): + if context is None: + context = {} + result = {} + location = self.pool.get('ir.config_parameter').get_param('ir_attachment.location') + bin_size = context.get('bin_size', False) + for i in self.browse(cr, uid, ids, context=context): + if location and i.store_fname: + result[i.id] = self._file_read(cr, uid, location, i.store_fname, bin_size) + else: + result[i.id] = i.db_datas + return result + + def _data_set(self, cr, uid, id, name, value, arg, context=None): + if not value: + return True + if context is None: + context = {} + result = {} + location = self.pool.get('ir.config_parameter').get_param('ir_attachment.location') + bin_size = context.get('bin_size', False) + if path: + i = self.browse(cr, uid, id, context=context) + if i.store_fname: + self._file_delete(cr, uid, location, i.store_fname) + fname = self._file_write(cr, uid, location, value) + self.write(cr, uid, [id], {'store_fname': fname}, context=context) + else: + self.write(cr, uid, [id], {'db_datas': value}, context=context) + return True + _name = 'ir.attachment' _columns = { 'name': fields.char('Attachment Name',size=256, required=True), - 'datas': fields.binary('Data'), 'datas_fname': fields.char('File Name',size=256), 'description': fields.text('Description'), - 'res_name': fields.function(_name_get_resname, type='char', size=128, - string='Resource Name', store=True), - 'res_model': fields.char('Resource Object',size=64, readonly=True, - help="The database object this attachment will be attached to"), - 'res_id': fields.integer('Resource ID', readonly=True, - help="The record id this is attached to"), - 'url': fields.char('Url', size=512, oldname="link"), - 'type': fields.selection( - [ ('url','URL'), ('binary','Binary'), ], - 'Type', help="Binary File or external URL", required=True, change_default=True), - + 'res_name': fields.function(_name_get_resname, type='char', size=128, string='Resource Name', store=True), + 'res_model': fields.char('Resource Model',size=64, readonly=True, help="The database object this attachment will be attached to"), + 'res_id': fields.integer('Resource ID', readonly=True, help="The record id this is attached to"), 'create_date': fields.datetime('Date Created', readonly=True), 'create_uid': fields.many2one('res.users', 'Owner', readonly=True), 'company_id': fields.many2one('res.company', 'Company', change_default=True), + 'type': fields.selection( [ ('url','URL'), ('binary','Binary'), ], + 'Type', help="Binary File or URL", required=True, change_default=True), + 'url': fields.char('Url', size=1024), + # al: For the moment I keepi those shitty field names for backward compatibility with document + 'datas': fields.function(_data_get, fnct_inv=_data_set, string='File Content', type="binary", nodrop=True), + 'store_fname': fields.char('Stored Filename', size=256), + 'db_datas': fields.binary('Database Data'), } _defaults = { From 807c949030d8b672e7a3da1ed2819d8886c92710 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sun, 16 Dec 2012 13:25:55 +0100 Subject: [PATCH 3/8] minor fixes bzr revid: al@openerp.com-20121216122555-frwadjd247zidw7o --- openerp/addons/base/ir/ir_attachment.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index 0a4ec979103..644cec10bf0 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -99,9 +99,9 @@ class ir_attachment(osv.osv): _logger.error("_file_write writing %s",full_path) return fname - def _file_delete(self, cr, uid, location, fname, threshold=1): + def _file_delete(self, cr, uid, location, fname): count = self.search(cr, 1, [('store_fname','=',fname)], count=True) - if count <= threshold: + if count <= 1: full_path = self._full_path(cr, uid, location, fname) try: os.unlink(full_path) @@ -122,14 +122,13 @@ class ir_attachment(osv.osv): return result def _data_set(self, cr, uid, id, name, value, arg, context=None): + # We dont handle setting data to null if not value: return True if context is None: context = {} - result = {} location = self.pool.get('ir.config_parameter').get_param('ir_attachment.location') - bin_size = context.get('bin_size', False) - if path: + if location: i = self.browse(cr, uid, id, context=context) if i.store_fname: self._file_delete(cr, uid, location, i.store_fname) From 2569587c905bcdb01dd6f62b996e979626992851 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sun, 16 Dec 2012 18:38:56 +0100 Subject: [PATCH 4/8] Simplify openerp testing, deprecated redondant test declaration, we feed all tests.* modules to unittest2.TestLoader.loadTestsFromModule(module) bzr revid: al@openerp.com-20121216173856-euntlfi80uzutkp0 --- openerp/addons/base/__openerp__.py | 5 - openerp/addons/base/ir/ir_attachment.py | 10 +- openerp/addons/base/test/base_test.xml | 14 --- openerp/addons/base/test/test_ir_cron.py | 116 ------------------ openerp/addons/base/test/test_ir_cron.yml | 61 --------- openerp/addons/base/tests/__init__.py | 14 +-- .../addons/base/tests/test_ir_attachment.py | 16 +++ openerp/modules/module.py | 97 +++------------ openerp/tests/__init__.py | 36 ++---- 9 files changed, 58 insertions(+), 311 deletions(-) delete mode 100644 openerp/addons/base/test/base_test.xml delete mode 100644 openerp/addons/base/test/test_ir_cron.py delete mode 100644 openerp/addons/base/test/test_ir_cron.yml create mode 100644 openerp/addons/base/tests/test_ir_attachment.py diff --git a/openerp/addons/base/__openerp__.py b/openerp/addons/base/__openerp__.py index b4473434cdf..d400b28a729 100644 --- a/openerp/addons/base/__openerp__.py +++ b/openerp/addons/base/__openerp__.py @@ -93,16 +93,11 @@ The kernel of OpenERP, needed for all installation. 'res/res_partner_image_demo.xml', ], 'test': [ - 'test/base_test.xml', 'test/base_test.yml', 'test/test_context.xml', 'test/bug_lp541545.xml', 'test/test_osv_expression.yml', 'test/test_ir_rule.yml', # <-- These tests modify/add/delete ir_rules. - # Commented because this takes some time. - # This must be (un)commented with the corresponding import statement - # in test/__init__.py. - # 'test/test_ir_cron.yml', # <-- These tests perform a roolback. ], 'installable': True, 'auto_install': True, diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index 644cec10bf0..4b9a721e7b5 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -112,13 +112,13 @@ class ir_attachment(osv.osv): if context is None: context = {} result = {} - location = self.pool.get('ir.config_parameter').get_param('ir_attachment.location') + location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location') bin_size = context.get('bin_size', False) - for i in self.browse(cr, uid, ids, context=context): - if location and i.store_fname: + for attach in self.browse(cr, uid, ids, context=context): + if location and attach.store_fname: result[i.id] = self._file_read(cr, uid, location, i.store_fname, bin_size) else: - result[i.id] = i.db_datas + result[i.id] = attach.db_datas return result def _data_set(self, cr, uid, id, name, value, arg, context=None): @@ -127,7 +127,7 @@ class ir_attachment(osv.osv): return True if context is None: context = {} - location = self.pool.get('ir.config_parameter').get_param('ir_attachment.location') + location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location') if location: i = self.browse(cr, uid, id, context=context) if i.store_fname: diff --git a/openerp/addons/base/test/base_test.xml b/openerp/addons/base/test/base_test.xml deleted file mode 100644 index 27bcabfc8f9..00000000000 --- a/openerp/addons/base/test/base_test.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - ASUSTeK - - - - \ No newline at end of file diff --git a/openerp/addons/base/test/test_ir_cron.py b/openerp/addons/base/test/test_ir_cron.py deleted file mode 100644 index 6ef79ac3a78..00000000000 --- a/openerp/addons/base/test/test_ir_cron.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2011-TODAY OpenERP S.A. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -import time -from datetime import datetime -from dateutil.relativedelta import relativedelta - -import openerp - -JOB = { - 'function': u'_0_seconds', - 'interval_type': u'minutes', - 'user_id': 1, - 'name': u'test', - 'args': False, - 'numbercall': 1, - 'nextcall': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), - 'priority': 5, - 'doall': True, - 'active': True, - 'interval_number': 1, - 'model': u'ir.cron' -} - -class test_ir_cron(openerp.osv.osv.osv): - """ Add a few handy methods to test cron jobs scheduling. """ - _inherit = "ir.cron" - - def _0_seconds(a, b, c): - print ">>> _0_seconds" - - def _20_seconds(self, cr, uid): - print ">>> in _20_seconds" - time.sleep(20) - print ">>> out _20_seconds" - - def _80_seconds(self, cr, uid): - print ">>> in _80_seconds" - time.sleep(80) - print ">>> out _80_seconds" - - def test_0(self, cr, uid): - now = datetime.now() - t1 = (now + relativedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') - t2 = (now + relativedelta(minutes=1, seconds=5)).strftime('%Y-%m-%d %H:%M:%S') - t3 = (now + relativedelta(minutes=1, seconds=10)).strftime('%Y-%m-%d %H:%M:%S') - self.create(cr, uid, dict(JOB, name='test_0 _20_seconds A', function='_20_seconds', nextcall=t1)) - self.create(cr, uid, dict(JOB, name='test_0 _20_seconds B', function='_20_seconds', nextcall=t2)) - self.create(cr, uid, dict(JOB, name='test_0 _20_seconds C', function='_20_seconds', nextcall=t3)) - - def test_1(self, cr, uid): - now = datetime.now() - t1 = (now + relativedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') - self.create(cr, uid, dict(JOB, name='test_1 _20_seconds * 3', function='_20_seconds', nextcall=t1, numbercall=3)) - - def test_2(self, cr, uid): - now = datetime.now() - t1 = (now + relativedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') - self.create(cr, uid, dict(JOB, name='test_2 _80_seconds * 2', function='_80_seconds', nextcall=t1, numbercall=2)) - - def test_3(self, cr, uid): - now = datetime.now() - t1 = (now + relativedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') - t2 = (now + relativedelta(minutes=1, seconds=5)).strftime('%Y-%m-%d %H:%M:%S') - t3 = (now + relativedelta(minutes=1, seconds=10)).strftime('%Y-%m-%d %H:%M:%S') - self.create(cr, uid, dict(JOB, name='test_3 _80_seconds A', function='_80_seconds', nextcall=t1)) - self.create(cr, uid, dict(JOB, name='test_3 _20_seconds B', function='_20_seconds', nextcall=t2)) - self.create(cr, uid, dict(JOB, name='test_3 _20_seconds C', function='_20_seconds', nextcall=t3)) - - # This test assumes 4 cron threads. - def test_00(self, cr, uid): - self.test_00_set = set() - now = datetime.now() - t1 = (now + relativedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') - t2 = (now + relativedelta(minutes=1, seconds=5)).strftime('%Y-%m-%d %H:%M:%S') - t3 = (now + relativedelta(minutes=1, seconds=10)).strftime('%Y-%m-%d %H:%M:%S') - self.create(cr, uid, dict(JOB, name='test_00 _20_seconds_A', function='_20_seconds_A', nextcall=t1)) - self.create(cr, uid, dict(JOB, name='test_00 _20_seconds_B', function='_20_seconds_B', nextcall=t2)) - self.create(cr, uid, dict(JOB, name='test_00 _20_seconds_C', function='_20_seconds_C', nextcall=t3)) - - def _expect(self, cr, uid, to_add, to_sleep, to_expect_in, to_expect_out): - assert self.test_00_set == to_expect_in - self.test_00_set.add(to_add) - time.sleep(to_sleep) - self.test_00_set.discard(to_add) - assert self.test_00_set == to_expect_out - - def _20_seconds_A(self, cr, uid): - self._expect(cr, uid, 'A', 20, set(), set(['B', 'C'])) - - def _20_seconds_B(self, cr, uid): - self._expect(cr, uid, 'B', 20, set('A'), set('C')) - - def _20_seconds_C(self, cr, uid): - self._expect(cr, uid, 'C', 20, set(['A', 'B']), set()) - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/openerp/addons/base/test/test_ir_cron.yml b/openerp/addons/base/test/test_ir_cron.yml deleted file mode 100644 index 16d648e1c30..00000000000 --- a/openerp/addons/base/test/test_ir_cron.yml +++ /dev/null @@ -1,61 +0,0 @@ -- - Test the cron jobs scheduling. -- - Disable the existing cron jobs if any during the tests. -- - !python {model: ir.cron }: | - # For this test to work, as it involves multiple database cursors, - # we have to commit changes. But YAML tests must be rollbacked, so - # the final database state is left untouched. So we have to be a bit - # ugly here: use our own cursor, commit, and clean after ourselves. - # We also pass around some ids using setattr/delattr, and we have to - # rollback the previous tests otherwise we won't be able to touch the - # db. - # Well, this should probably be a standalone, or regular unit test, - # instead of using the YAML infrastructure. - cr.rollback() - our_cr = self.pool.db.cursor() - try: - ids = self.search(our_cr, uid, [], {}) - setattr(self, 'saved_ids', ids) - self.write(our_cr, uid, ids, {'active': False}, {}) - our_cr.commit() - finally: - our_cr.close() -- - Three concurrent jobs started with a slight time gap. Assume 4 cron threads. - This will take about 2 minutes. -- - !python {model: ir.cron }: | - # Pretend initialization is already done. We the use a try/finally - # to reset _init correctly. - self.pool._init = False - our_cr = self.pool.db.cursor() - try: - self.test_00(our_cr, uid) # this will commit using the passed cursor - import openerp.cron - openerp.cron._thread_slots = 4 - # Wake up this db as soon as the master cron thread starts. - openerp.cron.schedule_wakeup(1, self.pool.db.dbname) - # Pretend to be the master thread, for 4 iterations. - openerp.cron.runner_body() - openerp.cron.runner_body() - openerp.cron.runner_body() - openerp.cron.runner_body() - finally: - self.pool._init = True - our_cr.close() -- - Clean after ourselves. -- - !python {model: ir.cron }: | - our_cr = self.pool.db.cursor() - try: - ids = [x for x in self.search(our_cr, uid, ['|', ('active', '=', True), ('active', '=', False)], {}) if x not in self.saved_ids] - self.unlink(our_cr, uid, ids, {}) - ids = self.saved_ids - delattr(self, 'saved_ids') - self.write(our_cr, uid, ids, {'active': True}, {}) - our_cr.commit() - finally: - our_cr.close() diff --git a/openerp/addons/base/tests/__init__.py b/openerp/addons/base/tests/__init__.py index 70f9a932d01..1d60f1a60a8 100644 --- a/openerp/addons/base/tests/__init__.py +++ b/openerp/addons/base/tests/__init__.py @@ -1,9 +1,7 @@ -import test_base, test_expression, test_search, test_ir_values, test_menu +#import test_base +#import test_expression +import test_ir_attachment +#import test_ir_values +#import test_menu +#import test_search -checks = [ - test_base, - test_expression, - test_search, - test_ir_values, - test_menu, -] diff --git a/openerp/addons/base/tests/test_ir_attachment.py b/openerp/addons/base/tests/test_ir_attachment.py new file mode 100644 index 00000000000..fa04a4f967c --- /dev/null +++ b/openerp/addons/base/tests/test_ir_attachment.py @@ -0,0 +1,16 @@ +import unittest2 + +import openerp.tests.common as common + +class test_ir_attachment(common.TransactionCase): + + def test_00_attachment_flow(self): + + registry, cr, uid = self.registry, self.cr, self.uid + + ira = registry('ir.attachment') + d = ira.create(cr, uid, {'name': 'a1', 'datas':'blob1'}) + + #name_asc = partners.search(cr, uid, [('name', 'like', 'test_search_order%')], order="name asc") + #self.assertEqual([a, ab, b, c], name_asc, "Search with 'NAME ASC' order failed.") + diff --git a/openerp/modules/module.py b/openerp/modules/module.py index bb4ff481fb9..aefab86796e 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -456,15 +456,9 @@ def get_modules_with_version(): continue return res -def get_test_modules(module, submodule, explode): - """ - Return a list of submodules containing tests. - `submodule` can be: - - None - - the name of a submodule - - '__fast_suite__' - - '__sanity_checks__' - """ +def get_test_modules(module): + """ Return a list of module for the addons potentialy containing tests to + feed unittest2.TestLoader.loadTestsFromModule() """ # Turn command-line module, submodule into importable names. if module is None: pass @@ -472,74 +466,21 @@ def get_test_modules(module, submodule, explode): module = 'openerp.tests' else: module = 'openerp.addons.' + module + '.tests' - # Try to import the module try: __import__(module) except Exception, e: - if explode: - print 'Can not `import %s`.' % module - import logging - logging.exception('') - sys.exit(1) - else: - if str(e) == 'No module named tests': - # It seems the module has no `tests` sub-module, no problem. - pass - else: - _logger.exception('Can not `import %s`.', module) - return [] + # If module has no `tests` sub-module, no problem. + if str(e) != 'No module named tests': + _logger.exception('Can not `import %s`.', module) + return [] - # Discover available test sub-modules. - m = sys.modules[module] - submodule_names = sorted([x for x in dir(m) \ - if x.startswith('test_') and \ - isinstance(getattr(m, x), types.ModuleType)]) - submodules = [getattr(m, x) for x in submodule_names] - - def show_submodules_and_exit(): - if submodule_names: - print 'Available submodules are:' - for x in submodule_names: - print ' ', x - sys.exit(1) - - if submodule is None: - # Use auto-discovered sub-modules. - ms = submodules - elif submodule == '__fast_suite__': - # Obtain the explicit test sub-modules list. - ms = getattr(sys.modules[module], 'fast_suite', None) - # `suite` was used before the 6.1 release instead of `fast_suite`. - ms = ms if ms else getattr(sys.modules[module], 'suite', None) - if ms is None: - if explode: - print 'The module `%s` has no defined test suite.' % (module,) - show_submodules_and_exit() - else: - ms = [] - elif submodule == '__sanity_checks__': - ms = getattr(sys.modules[module], 'checks', None) - if ms is None: - if explode: - print 'The module `%s` has no defined sanity checks.' % (module,) - show_submodules_and_exit() - else: - ms = [] - else: - # Pick the command-line-specified test sub-module. - m = getattr(sys.modules[module], submodule, None) - ms = [m] - - if m is None: - if explode: - print 'The module `%s` has no submodule named `%s`.' % \ - (module, submodule) - show_submodules_and_exit() - else: - ms = [] - - return ms + # include submodules too + result = [] + for name in sys.modules: + if name.startswith(module): + result.append(sys.modules[name]) + return result def run_unit_tests(module_name): """ @@ -547,14 +488,9 @@ def run_unit_tests(module_name): Return None if no test was found. """ import unittest2 - ms = get_test_modules(module_name, '__fast_suite__', explode=False) - # TODO: No need to try again if the above call failed because of e.g. a syntax error. - ms.extend(get_test_modules(module_name, '__sanity_checks__', explode=False)) - suite = unittest2.TestSuite() - for m in ms: - suite.addTests(unittest2.TestLoader().loadTestsFromModule(m)) + ms = get_test_modules(module_name) if ms: - _logger.log(logging.TEST, 'module %s: executing %s `fast_suite` and/or `checks` sub-modules', module_name, len(ms)) + _logger.log(logging.TEST, 'module %s: running test.', module_name) # Use a custom stream object to log the test executions. class MyStream(object): def __init__(self): @@ -570,6 +506,9 @@ def run_unit_tests(module_name): c = '` ' + c first = False _logger.log(logging.TEST, c) + suite = unittest2.TestSuite() + for m in ms: + suite.addTests(unittest2.TestLoader().loadTestsFromModule(m)) result = unittest2.TextTestRunner(verbosity=2, stream=MyStream()).run(suite) if result.wasSuccessful(): return True diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py index 92a40ab306c..38569464bbe 100644 --- a/openerp/tests/__init__.py +++ b/openerp/tests/__init__.py @@ -8,28 +8,18 @@ Tests can be explicitely added to the `fast_suite` or `checks` lists or not. See the :ref:`test-framework` section in the :ref:`features` list. """ -from . import test_expression, test_mail, test_ir_sequence, test_orm, \ - test_fields, test_basecase, \ - test_view_validation, test_uninstall, test_misc, test_db_cursor, \ - test_osv, test_translate -from . import test_ir_filters - -fast_suite = [ - test_ir_sequence, - test_ir_filters -] - -checks = [ - test_expression, - test_mail, - test_db_cursor, - test_orm, - test_fields, - test_basecase, - test_view_validation, - test_misc, - test_osv, - test_translate, -] +#import test_basecase +#import test_db_cursor +#import test_expression +#import test_fields +#import test_ir_filters +#import test_ir_sequence +#import test_mail +#import test_misc +#import test_orm +#import test_osv +#import test_translate +#import test_uninstall +#import test_view_validation # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 70aa2c995a53208a9e4aabf85f0c9ee210c61506 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sun, 16 Dec 2012 20:03:17 +0100 Subject: [PATCH 5/8] filestore testcase and fix bugs bzr revid: al@openerp.com-20121216190317-xoz0jij2cfk0c2zk --- openerp/addons/base/ir/ir_attachment.py | 23 +++-- openerp/addons/base/tests/__init__.py | 10 +-- .../addons/base/tests/test_ir_attachment.py | 87 +++++++++++++++++-- openerp/tests/__init__.py | 26 +++--- 4 files changed, 114 insertions(+), 32 deletions(-) diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index 4b9a721e7b5..f9502b7a288 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -19,7 +19,10 @@ # ############################################################################## +import hashlib import itertools +import os +import re from osv import fields,osv from osv.orm import except_orm @@ -65,7 +68,7 @@ class ir_attachment(osv.osv): # sanitize location name and path location = re.sub('[.]','',location) - location = path.strip('/\\') + location = location.strip('/\\') path = re.sub('[.]','',path) path = path.strip('/\\') @@ -106,6 +109,7 @@ class ir_attachment(osv.osv): try: os.unlink(full_path) except IOError: + # Harmless and needed for race conditions _logger.error("_file_delete could not unlink %s",full_path) def _data_get(self, cr, uid, ids, name, arg, context=None): @@ -113,12 +117,12 @@ class ir_attachment(osv.osv): context = {} result = {} location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location') - bin_size = context.get('bin_size', False) + bin_size = context.get('bin_size') for attach in self.browse(cr, uid, ids, context=context): if location and attach.store_fname: - result[i.id] = self._file_read(cr, uid, location, i.store_fname, bin_size) + result[attach.id] = self._file_read(cr, uid, location, attach.store_fname, bin_size) else: - result[i.id] = attach.db_datas + result[attach.id] = attach.db_datas return result def _data_set(self, cr, uid, id, name, value, arg, context=None): @@ -129,9 +133,9 @@ class ir_attachment(osv.osv): context = {} location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location') if location: - i = self.browse(cr, uid, id, context=context) - if i.store_fname: - self._file_delete(cr, uid, location, i.store_fname) + attach = self.browse(cr, uid, id, context=context) + if attach.store_fname: + self._file_delete(cr, uid, location, attach.store_fname) fname = self._file_write(cr, uid, location, value) self.write(cr, uid, [id], {'store_fname': fname}, context=context) else: @@ -263,6 +267,11 @@ class ir_attachment(osv.osv): def unlink(self, cr, uid, ids, context=None): self.check(cr, uid, ids, 'unlink', context=context) + location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location') + if location: + for attach in self.browse(cr, uid, ids, context=context): + if attach.store_fname: + self._file_delete(cr, uid, location, attach.store_fname) return super(ir_attachment, self).unlink(cr, uid, ids, context) def create(self, cr, uid, values, context=None): diff --git a/openerp/addons/base/tests/__init__.py b/openerp/addons/base/tests/__init__.py index 1d60f1a60a8..4a586a8a971 100644 --- a/openerp/addons/base/tests/__init__.py +++ b/openerp/addons/base/tests/__init__.py @@ -1,7 +1,7 @@ -#import test_base -#import test_expression +import test_base +import test_expression import test_ir_attachment -#import test_ir_values -#import test_menu -#import test_search +import test_ir_values +import test_menu +import test_search diff --git a/openerp/addons/base/tests/test_ir_attachment.py b/openerp/addons/base/tests/test_ir_attachment.py index fa04a4f967c..4d96ec42b28 100644 --- a/openerp/addons/base/tests/test_ir_attachment.py +++ b/openerp/addons/base/tests/test_ir_attachment.py @@ -1,16 +1,89 @@ +import hashlib +import os + import unittest2 -import openerp.tests.common as common +import openerp +import openerp.tests.common -class test_ir_attachment(common.TransactionCase): +class test_ir_attachment(openerp.tests.common.TransactionCase): def test_00_attachment_flow(self): - registry, cr, uid = self.registry, self.cr, self.uid - + root_path = openerp.tools.config['root_path'] ira = registry('ir.attachment') - d = ira.create(cr, uid, {'name': 'a1', 'datas':'blob1'}) - #name_asc = partners.search(cr, uid, [('name', 'like', 'test_search_order%')], order="name asc") - #self.assertEqual([a, ab, b, c], name_asc, "Search with 'NAME ASC' order failed.") + # Blob1 + blob1 = 'blob1' + blob1_b64 = blob1.encode('base64') + blob1_hash = hashlib.sha1(blob1).hexdigest() + blob1_fname = blob1_hash[:3] + '/' + blob1_hash + + # Blob2 + blob2 = 'blob2' + blob2_b64 = blob2.encode('base64') + blob2_hash = hashlib.sha1(blob2).hexdigest() + blob2_fname = blob2_hash[:3] + '/' + blob2_hash + + # 'ir_attachment.location' is undefined test database storage + a1 = ira.create(cr, uid, {'name': 'a1', 'datas': blob1_b64}) + a1_read = ira.read(cr, uid, [a1], ['datas']) + self.assertEqual(a1_read[0]['datas'], blob1_b64) + + cr.execute("select id,db_datas from ir_attachment where id = %s", (a1,) ) + a1_db_datas = str(cr.fetchall()[0][1]) + self.assertEqual(a1_db_datas, blob1_b64) + + # define a location for filestore + registry('ir.config_parameter').set_param(cr, uid, 'ir_attachment.location', 'file:///filestore') + + # Test file storage + a2 = ira.create(cr, uid, {'name': 'a2', 'datas': blob1_b64}) + a2_read = ira.read(cr, uid, [a2], ['datas']) + self.assertEqual(a2_read[0]['datas'], blob1_b64) + + cr.execute("select id,store_fname from ir_attachment where id = %s", (a2,) ) + a2_store_fname = cr.fetchall()[0][1] + self.assertEqual(a2_store_fname, blob1_fname) + + a2_fn = os.path.join(root_path, 'filestore', cr.dbname, blob1_hash[:3], blob1_hash) + fc = file(a2_fn).read() + self.assertEqual(fc, blob1) + + # create a3 with same blob + a3 = ira.create(cr, uid, {'name': 'a3', 'datas': blob1_b64}) + a3_read = ira.read(cr, uid, [a3], ['datas']) + self.assertEqual(a3_read[0]['datas'], blob1_b64) + + cr.execute("select id,store_fname from ir_attachment where id = %s", (a3,) ) + a3_store_fname = cr.fetchall()[0][1] + self.assertEqual(a3_store_fname, a2_store_fname) + + # create a4 blob2 + a4 = ira.create(cr, uid, {'name': 'a4', 'datas': blob2_b64}) + a4_read = ira.read(cr, uid, [a4], ['datas']) + self.assertEqual(a4_read[0]['datas'], blob2_b64) + + a4_fn = os.path.join(root_path, 'filestore', cr.dbname, blob2_hash[:3], blob2_hash) + self.assertTrue(os.path.isfile(a4_fn)) + + # delete a3 but file stays + ira.unlink(cr, uid, [a3]) + self.assertTrue(os.path.isfile(a2_fn)) + + # delete a2 it is unlinked + ira.unlink(cr, uid, [a2]) + self.assertFalse(os.path.isfile(a2_fn)) + + # update a4 blob2 by blob1 + ira.write(cr, uid, [a4], {'datas': blob1_b64}) + a4_read = ira.read(cr, uid, [a4], ['datas']) + self.assertEqual(a4_read[0]['datas'], blob1_b64) + + # file of a4 disapear and a2 reappear + self.assertFalse(os.path.isfile(a4_fn)) + self.assertTrue(os.path.isfile(a2_fn)) + + # everybody applause + diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py index 38569464bbe..327b8715125 100644 --- a/openerp/tests/__init__.py +++ b/openerp/tests/__init__.py @@ -8,18 +8,18 @@ Tests can be explicitely added to the `fast_suite` or `checks` lists or not. See the :ref:`test-framework` section in the :ref:`features` list. """ -#import test_basecase -#import test_db_cursor -#import test_expression -#import test_fields -#import test_ir_filters -#import test_ir_sequence -#import test_mail -#import test_misc -#import test_orm -#import test_osv -#import test_translate -#import test_uninstall -#import test_view_validation +import test_basecase +import test_db_cursor +import test_expression +import test_fields +import test_ir_filters +import test_ir_sequence +import test_mail +import test_misc +import test_orm +import test_osv +import test_translate +import test_uninstall +import test_view_validation # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 9568552636d866b2c558cfc800e9ede902482001 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sun, 16 Dec 2012 20:39:32 +0100 Subject: [PATCH 6/8] revert test change will be moved to an other branch bzr revid: al@openerp.com-20121216193932-foluon7b5bqqvrib --- openerp/addons/base/tests/__init__.py | 8 +++ openerp/modules/module.py | 97 ++++++++++++++++++++++----- openerp/tests/__init__.py | 18 +++++ 3 files changed, 105 insertions(+), 18 deletions(-) diff --git a/openerp/addons/base/tests/__init__.py b/openerp/addons/base/tests/__init__.py index 4a586a8a971..d891bc06fda 100644 --- a/openerp/addons/base/tests/__init__.py +++ b/openerp/addons/base/tests/__init__.py @@ -5,3 +5,11 @@ import test_ir_values import test_menu import test_search +checks = [ + test_base, + test_expression, + test_ir_attachment, + test_ir_values, + test_menu, + test_search, +] diff --git a/openerp/modules/module.py b/openerp/modules/module.py index aefab86796e..bb4ff481fb9 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -456,9 +456,15 @@ def get_modules_with_version(): continue return res -def get_test_modules(module): - """ Return a list of module for the addons potentialy containing tests to - feed unittest2.TestLoader.loadTestsFromModule() """ +def get_test_modules(module, submodule, explode): + """ + Return a list of submodules containing tests. + `submodule` can be: + - None + - the name of a submodule + - '__fast_suite__' + - '__sanity_checks__' + """ # Turn command-line module, submodule into importable names. if module is None: pass @@ -466,21 +472,74 @@ def get_test_modules(module): module = 'openerp.tests' else: module = 'openerp.addons.' + module + '.tests' + # Try to import the module try: __import__(module) except Exception, e: - # If module has no `tests` sub-module, no problem. - if str(e) != 'No module named tests': - _logger.exception('Can not `import %s`.', module) - return [] + if explode: + print 'Can not `import %s`.' % module + import logging + logging.exception('') + sys.exit(1) + else: + if str(e) == 'No module named tests': + # It seems the module has no `tests` sub-module, no problem. + pass + else: + _logger.exception('Can not `import %s`.', module) + return [] - # include submodules too - result = [] - for name in sys.modules: - if name.startswith(module): - result.append(sys.modules[name]) - return result + # Discover available test sub-modules. + m = sys.modules[module] + submodule_names = sorted([x for x in dir(m) \ + if x.startswith('test_') and \ + isinstance(getattr(m, x), types.ModuleType)]) + submodules = [getattr(m, x) for x in submodule_names] + + def show_submodules_and_exit(): + if submodule_names: + print 'Available submodules are:' + for x in submodule_names: + print ' ', x + sys.exit(1) + + if submodule is None: + # Use auto-discovered sub-modules. + ms = submodules + elif submodule == '__fast_suite__': + # Obtain the explicit test sub-modules list. + ms = getattr(sys.modules[module], 'fast_suite', None) + # `suite` was used before the 6.1 release instead of `fast_suite`. + ms = ms if ms else getattr(sys.modules[module], 'suite', None) + if ms is None: + if explode: + print 'The module `%s` has no defined test suite.' % (module,) + show_submodules_and_exit() + else: + ms = [] + elif submodule == '__sanity_checks__': + ms = getattr(sys.modules[module], 'checks', None) + if ms is None: + if explode: + print 'The module `%s` has no defined sanity checks.' % (module,) + show_submodules_and_exit() + else: + ms = [] + else: + # Pick the command-line-specified test sub-module. + m = getattr(sys.modules[module], submodule, None) + ms = [m] + + if m is None: + if explode: + print 'The module `%s` has no submodule named `%s`.' % \ + (module, submodule) + show_submodules_and_exit() + else: + ms = [] + + return ms def run_unit_tests(module_name): """ @@ -488,9 +547,14 @@ def run_unit_tests(module_name): Return None if no test was found. """ import unittest2 - ms = get_test_modules(module_name) + ms = get_test_modules(module_name, '__fast_suite__', explode=False) + # TODO: No need to try again if the above call failed because of e.g. a syntax error. + ms.extend(get_test_modules(module_name, '__sanity_checks__', explode=False)) + suite = unittest2.TestSuite() + for m in ms: + suite.addTests(unittest2.TestLoader().loadTestsFromModule(m)) if ms: - _logger.log(logging.TEST, 'module %s: running test.', module_name) + _logger.log(logging.TEST, 'module %s: executing %s `fast_suite` and/or `checks` sub-modules', module_name, len(ms)) # Use a custom stream object to log the test executions. class MyStream(object): def __init__(self): @@ -506,9 +570,6 @@ def run_unit_tests(module_name): c = '` ' + c first = False _logger.log(logging.TEST, c) - suite = unittest2.TestSuite() - for m in ms: - suite.addTests(unittest2.TestLoader().loadTestsFromModule(m)) result = unittest2.TextTestRunner(verbosity=2, stream=MyStream()).run(suite) if result.wasSuccessful(): return True diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py index 327b8715125..07232bff5dc 100644 --- a/openerp/tests/__init__.py +++ b/openerp/tests/__init__.py @@ -22,4 +22,22 @@ import test_translate import test_uninstall import test_view_validation +fast_suite = [ + test_ir_sequence, + test_ir_filters +] + +checks = [ + test_expression, + test_mail, + test_db_cursor, + test_orm, + test_fields, + test_basecase, + test_view_validation, + test_misc, + test_osv, + test_translate, +] + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From fde832955ab4155087893db31aeb9ac3aa7c4466 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Mon, 17 Dec 2012 02:16:01 +0100 Subject: [PATCH 7/8] add file_size bzr revid: al@openerp.com-20121217011601-utt2a0zweaq2c0bt --- openerp/addons/base/ir/ir_attachment.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index f9502b7a288..014a56380bc 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -132,14 +132,15 @@ class ir_attachment(osv.osv): if context is None: context = {} location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location') + file_size = len(value.decode('base64')) if location: attach = self.browse(cr, uid, id, context=context) if attach.store_fname: self._file_delete(cr, uid, location, attach.store_fname) fname = self._file_write(cr, uid, location, value) - self.write(cr, uid, [id], {'store_fname': fname}, context=context) + super(ir_attachment, self).write(cr, uid, [id], {'store_fname': fname, 'file_size': file_size}, context=context) else: - self.write(cr, uid, [id], {'db_datas': value}, context=context) + super(ir_attachment, self).write(cr, uid, [id], {'db_datas': value, 'file_size': file_size}, context=context) return True _name = 'ir.attachment' @@ -156,14 +157,16 @@ class ir_attachment(osv.osv): 'type': fields.selection( [ ('url','URL'), ('binary','Binary'), ], 'Type', help="Binary File or URL", required=True, change_default=True), 'url': fields.char('Url', size=1024), - # al: For the moment I keepi those shitty field names for backward compatibility with document + # al: We keep shitty field names for backward compatibility with document 'datas': fields.function(_data_get, fnct_inv=_data_set, string='File Content', type="binary", nodrop=True), 'store_fname': fields.char('Stored Filename', size=256), 'db_datas': fields.binary('Database Data'), + 'file_size': fields.integer('File Size'), } _defaults = { 'type': 'binary', + 'file_size': 0, 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'ir.attachment', context=c), } @@ -259,6 +262,8 @@ class ir_attachment(osv.osv): def write(self, cr, uid, ids, vals, context=None): self.check(cr, uid, ids, 'write', context=context, values=vals) + if 'file_size' in vals: + del vals['file_size'] return super(ir_attachment, self).write(cr, uid, ids, vals, context) def copy(self, cr, uid, id, default=None, context=None): @@ -276,14 +281,13 @@ class ir_attachment(osv.osv): def create(self, cr, uid, values, context=None): self.check(cr, uid, [], mode='create', context=context, values=values) + if 'file_size' in values: + del values['file_size'] return super(ir_attachment, self).create(cr, uid, values, context) def action_get(self, cr, uid, context=None): return self.pool.get('ir.actions.act_window').for_xml_id( cr, uid, 'base', 'action_attachment', context=context) -ir_attachment() - - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 0ed151708ab3a4423e1d6a1ddcfed7e107113a33 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Thu, 20 Dec 2012 03:37:21 +0100 Subject: [PATCH 8/8] last fix bzr revid: al@openerp.com-20121220023721-oy7y4czmf3tvmyzl --- openerp/addons/base/ir/ir_attachment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index b73d8362c58..5f54b4aca8e 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -24,6 +24,7 @@ import itertools import os import re +from openerp import tools from openerp.osv import fields,osv class ir_attachment(osv.osv): @@ -77,7 +78,7 @@ class ir_attachment(osv.osv): r = '' try: if bin_size: - r = os.path.filesize(full_path) + r = os.path.getsize(full_path) else: r = open(full_path).read().encode('base64') except IOError: