From ff55f829ce88153dc54ba6f50323e43224662a69 Mon Sep 17 00:00:00 2001 From: OpenERP Date: Fri, 17 Dec 2010 13:38:22 +0100 Subject: [PATCH 01/13] [FIX] translate: correctly set the translation for menus indirectly named by their action and not visible in the simplified view. bzr revid: openerp@vmt-hp4520s-20101217123822-0g5h04fz92fxfn42 --- bin/tools/translate.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bin/tools/translate.py b/bin/tools/translate.py index 76d41d25054..a296f0e445d 100644 --- a/bin/tools/translate.py +++ b/bin/tools/translate.py @@ -933,7 +933,11 @@ def trans_load_data(db_name, fileobj, fileformat, lang, strict=False, lang_name= if obj: if field not in obj.fields_get_keys(cr, uid): continue - ids = obj.search(cr, uid, [(field, '=', dic['src'])]) + # Using search() instead of _search() will limit the + # returned list to the ids visible to uid, which is not + # in the extended view group. We want all of them and use + # _search(). + ids = obj._search(cr, uid, [(field, '=', dic['src'])]) # if the resource id (res_id) is in that list, use it, # otherwise use the whole list From 5918f087330bc9e6666b3fe13bfdba2815dfc9ee Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Sun, 19 Dec 2010 22:06:08 +0200 Subject: [PATCH 02/13] test_reports: first draft of a full action/wizard/report tester Use with a yaml test like: - I will test the action reports - !python {model: res.partner}: | from tools import test_reports reload(test_reports) # ;) test_reports.try_report_action(cr, uid, 'action_account_balance_menu', our_module='account') bzr revid: p_christ@hol.gr-20101219200608-ffl2k3qiexjxvp48 --- bin/tools/test_reports.py | 243 +++++++++++++++++++++++++++++++++++++- 1 file changed, 240 insertions(+), 3 deletions(-) diff --git a/bin/tools/test_reports.py b/bin/tools/test_reports.py index b6d0ec6b714..c52985df8c7 100644 --- a/bin/tools/test_reports.py +++ b/bin/tools/test_reports.py @@ -28,16 +28,21 @@ import netsvc import tools import logging +import pooler +from tools.safe_eval import safe_eval from subprocess import Popen, PIPE import os import tempfile -def try_report(cr, uid, rname, ids, data=None, context=None): +def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None): """ Try to render a report with contents of ids This function should also check for common pitfalls of reports. """ - log = logging.getLogger('tools.test_reports') + if our_module: + log = logging.getLogger('tests.%s' % our_module) + else: + log = logging.getLogger('tools.test_reports') if data is None: data = {} if context is None: @@ -46,7 +51,7 @@ def try_report(cr, uid, rname, ids, data=None, context=None): rname_s = rname[7:] else: rname_s = rname - log.debug("Trying %s.create(%r)", rname, ids) + log.log(netsvc.logging.TEST, " - Trying %s.create(%r)", rname, ids) res = netsvc.LocalService(rname).create(cr, uid, ids, data, context) if not isinstance(res, tuple): raise RuntimeError("Result of %s.create() should be a (data,format) tuple, now it is a %s" % \ @@ -86,6 +91,238 @@ def try_report(cr, uid, rname, ids, data=None, context=None): pass else: log.warning("Report %s produced a \"%s\" chunk, cannot examine it", rname, res_format) + return False + log.log(netsvc.logging.TEST, "Report %s produced correctly.", rname) return True + +def try_report_action(cr, uid, action_id, res_model=None, res_ids=None, + wiz_data=None, wiz_buttons=None, + context=None, our_module=None): + """Take an ir.action.act_window and follow it until a report is produced + + @param action_id the integer id of an action, or a reference to xml id + of the act_window (can search [our_module.]+xml_id + @param res_model, res_ids call the action as if it had been launched + from that model+ids (tree/form view action) + @param wiz_data a dictionary of values to use in the wizard, if needed. + They will override (or complete) the default values of the + wizard form. + @param wiz_buttons a list of button names, or button icon strings, which + should be preferred to press during the wizard. + Eg. 'OK' or 'gtk-print' + @param our_module the name of the calling module (string), like 'account' + """ + + if not our_module and isinstance(action_id, basestring): + if '.' in action_id: + our_module = action_id.split('.', 1)[0] + + if context is None: + context = {} + else: + context = context.copy() # keep it local + # TODO context fill-up + + pool = pooler.get_pool(cr.dbname) + if our_module: + log = logging.getLogger('tests.%s' % our_module) + else: + log = logging.getLogger('tools.test_reports') + + def log_test(msg, *args): + log.log(netsvc.logging.TEST, " - " + msg, *args) + + datas = {} + if res_model: + datas['model'] = res_model + if res_ids: + datas['ids'] = res_ids + + if not wiz_buttons: + wiz_buttons = [] + + if isinstance(action_id, basestring): + if '.' in action_id: + act_module, act_xmlid = action_id.split('.', 1) + else: + if not our_module: + raise ValueError('You cannot only specify action_id "%s" without a module name' % action_id) + act_module = our_module + act_xmlid = action_id + act_model, act_id = pool.get('ir.model.data').get_object_reference(cr, uid, act_module, act_xmlid) + else: + assert isinstance(action_id, (long, int)) + act_model = 'ir.action.act_window' # assume that + act_id = action_id + act_xmlid = '<%s>' % act_id + + def _exec_action(action, datas, context): + # taken from client/modules/action/main.py:84 _exec_action() + if isinstance(action, bool) or 'type' not in action: + return + # Updating the context : Adding the context of action in order to use it on Views called from buttons + if datas.get('id',False): + context.update( {'active_id': datas.get('id',False), 'active_ids': datas.get('ids',[]), 'active_model': datas.get('model',False)}) + context.update(safe_eval(action.get('context','{}'), context.copy())) + if action['type'] in ['ir.actions.act_window', 'ir.actions.submenu']: + for key in ('res_id', 'res_model', 'view_type', 'view_mode', + 'limit', 'auto_refresh', 'search_view', 'auto_search', 'search_view_id'): + datas[key] = action.get(key, datas.get(key, None)) + + view_id = False + if action.get('views', []): + if isinstance(action['views'],list): + view_id = action['views'][0][0] + datas['view_mode']= action['views'][0][1] + else: + if action.get('view_id', False): + view_id = action['view_id'][0] + elif action.get('view_id', False): + view_id = action['view_id'][0] + + assert datas['res_model'], "Cannot use the view without a model" + # Here, we have a view that we need to emulate + log_test("will emulate a %s view: %s#%s", + action['view_type'], datas['res_model'], view_id or '?') + + view_res = pool.get(datas['res_model']).fields_view_get(cr, uid, view_id, action['view_type'], context) + assert view_res and view_res.get('arch'), "Did not return any arch for the view" + view_data = {} + if view_res.get('fields',{}).keys(): + view_data = pool.get(datas['res_model']).default_get(cr, uid, view_res['fields'].keys(), context) + if datas.get('form'): + view_data.update(datas.get('form')) + if wiz_data: + view_data.update(wiz_data) + log.debug("View data is: %r", view_data) + + action_name = action.get('name') + try: + from xml.dom import minidom + cancel_found = False + buttons = [] + dom_doc = minidom.parseString(view_res['arch']) + if not action_name: + action_name = dom_doc.documentElement.getAttribute('name') + + for button in dom_doc.getElementsByTagName('button'): + button_weight = 0 + if button.getAttribute('special') == 'cancel': + cancel_found = True + continue + if button.getAttribute('icon') == 'gtk-cancel': + cancel_found = True + continue + if button.getAttribute('default_focus') == '1': + button_weight += 20 + if button.getAttribute('string') in wiz_buttons: + button_weight += 30 + elif button.getAttribute('icon') in wiz_buttons: + button_weight += 10 + string = button.getAttribute('string') or '?%s' % len(buttons) + + buttons.append( { 'name': button.getAttribute('name'), + 'string': string, + 'type': button.getAttribute('type'), + 'weight': button_weight, + }) + except Exception, e: + log.warning("Cannot resolve the view arch and locate the buttons!", exc_info=True) + raise AssertionError(e.args[0]) + + if not datas['res_id']: + # it is probably an orm_memory object, we need to create + # an instance + datas['res_id'] = pool.get(datas['res_model']).create(cr, uid, view_data, context) + + if not buttons: + raise AssertionError("view form doesn't have any buttons to press!") + + buttons.sort(key=lambda b: b['weight']) + log.debug('Buttons are: %s', ', '.join([ '%s: %d' % (b['string'], b['weight']) for b in buttons])) + + res = None + while buttons and not res: + b = buttons.pop() + log_test("in the %s form, I will press the %s button.", action_name, b['string']) + if not b['type']: + log_test("the %s button has no type, cannot use it", b['string']) + continue + if b['type'] == 'object': + #there we are! press the button! + fn = getattr(pool.get(datas['res_model']), b['name']) + if not fn: + log.error("The %s model doesn't have a %s attribute!", datas['res_model'], b['name']) + continue + res = fn(cr, uid, [datas['res_id'],], context) + break + else: + log.warning("in the %s form, the %s button has unknown type %s", + action_name, b['string'], b['type']) + return res + #elif action['type']=='ir.actions.server': + #res = rpc.session.rpc_exec_auth('/object', 'execute', 'ir.actions.server', 'run', [action['id']], context) + #if res: + #if not isinstance(res, list): + #res = [res] + #for r in res: + #self._exec_action(r, datas, context) + + #elif action['type']=='ir.actions.wizard': + #win=None + #if 'window' in datas: + #win=datas['window'] + #del datas['window'] + #wizard.execute(action['wiz_name'], datas, parent=win, context=context) + + #elif action['type']=='ir.actions.report.custom': + #if 'window' in datas: + #win=datas['window'] + #del datas['window'] + #datas['report_id'] = action['report_id'] + #self.exec_report('custom', datas, context) + + #elif action['type']=='ir.actions.report.int': + #if 'window' in datas: + #win=datas['window'] + #del datas['window'] + #self.exec_report(action['report_name'], datas) + + elif action['type']=='ir.actions.report.xml': + if 'window' in datas: + del datas['window'] + if not datas: + datas = action.get('datas',{}) + datas = datas.copy() + ids = datas.get('ids') + if 'ids' in datas: + del datas['ids'] + res = try_report(cr, uid, 'report.'+action['report_name'], ids, datas, context, our_module=our_module) + return res + else: + raise Exception("Cannot handle action of type %s" % act_model) + + log_test("will be using %s action %s #%d", act_model, act_xmlid, act_id) + action = pool.get(act_model).read(cr, uid, act_id, context=context) + assert action, "Could not read action %s[%s]" %(act_model, act_id) + loop = 0 + while action: + loop += 1 + # This part tries to emulate the loop of the Gtk client + if loop > 100: + log.error("Passed %d loops, giving up", loop) + raise Exception("Too many loops at action") + log_test("it is an %s action at loop #%d", action.get('type', 'unknown'), loop) + result = _exec_action(action, datas, context) + if not isinstance(result, dict): + break + datas = result.get('datas', {}) + if datas: + del result['datas'] + action = result + + return True + + #eof From 480a3441f30bbb2016f8b0202f08e9251860f8e5 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Sun, 19 Dec 2010 22:06:20 +0200 Subject: [PATCH 03/13] test_reports: rename res_model, res_ids to active_model, active_ids Requested by Odo, for consistency bzr revid: p_christ@hol.gr-20101219200620-0mldt3s19xg6j5w5 --- bin/tools/test_reports.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/tools/test_reports.py b/bin/tools/test_reports.py index c52985df8c7..e82df1be9fc 100644 --- a/bin/tools/test_reports.py +++ b/bin/tools/test_reports.py @@ -96,14 +96,14 @@ def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None): log.log(netsvc.logging.TEST, "Report %s produced correctly.", rname) return True -def try_report_action(cr, uid, action_id, res_model=None, res_ids=None, +def try_report_action(cr, uid, action_id, active_model=None, active_ids=None, wiz_data=None, wiz_buttons=None, context=None, our_module=None): """Take an ir.action.act_window and follow it until a report is produced @param action_id the integer id of an action, or a reference to xml id of the act_window (can search [our_module.]+xml_id - @param res_model, res_ids call the action as if it had been launched + @param active_model, active_ids call the action as if it had been launched from that model+ids (tree/form view action) @param wiz_data a dictionary of values to use in the wizard, if needed. They will override (or complete) the default values of the @@ -134,10 +134,10 @@ def try_report_action(cr, uid, action_id, res_model=None, res_ids=None, log.log(netsvc.logging.TEST, " - " + msg, *args) datas = {} - if res_model: - datas['model'] = res_model - if res_ids: - datas['ids'] = res_ids + if active_model: + datas['model'] = active_model + if active_ids: + datas['ids'] = active_ids if not wiz_buttons: wiz_buttons = [] From 656cb5753b39622b75e3ce7f0b9e2b1aa157ac34 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Sun, 19 Dec 2010 22:06:27 +0200 Subject: [PATCH 04/13] test_reports: aesthetic improvements for the test_report_action bzr revid: p_christ@hol.gr-20101219200627-7pju2glqrs53631u --- bin/tools/test_reports.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/tools/test_reports.py b/bin/tools/test_reports.py index e82df1be9fc..23076a0cd11 100644 --- a/bin/tools/test_reports.py +++ b/bin/tools/test_reports.py @@ -93,7 +93,7 @@ def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None): log.warning("Report %s produced a \"%s\" chunk, cannot examine it", rname, res_format) return False - log.log(netsvc.logging.TEST, "Report %s produced correctly.", rname) + log.log(netsvc.logging.TEST, " + Report %s produced correctly.", rname) return True def try_report_action(cr, uid, action_id, active_model=None, active_ids=None, @@ -245,9 +245,9 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None, res = None while buttons and not res: b = buttons.pop() - log_test("in the %s form, I will press the %s button.", action_name, b['string']) + log_test("in the \"%s\" form, I will press the \"%s\" button.", action_name, b['string']) if not b['type']: - log_test("the %s button has no type, cannot use it", b['string']) + log_test("the \"%s\" button has no type, cannot use it", b['string']) continue if b['type'] == 'object': #there we are! press the button! @@ -258,7 +258,7 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None, res = fn(cr, uid, [datas['res_id'],], context) break else: - log.warning("in the %s form, the %s button has unknown type %s", + log.warning("in the \"%s\" form, the \"%s\" button has unknown type %s", action_name, b['string'], b['type']) return res #elif action['type']=='ir.actions.server': From 1dbaae147b88f90d354b3a43632f298722e4a4f1 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Sun, 19 Dec 2010 22:07:51 +0200 Subject: [PATCH 05/13] ir.ui.menu: We don't need a "many2many_unique" class any more The parent many2many has been improved to avoid duplicate records. bzr revid: p_christ@hol.gr-20101219200751-0umvepzgtgjhnw78 --- bin/addons/base/ir/ir_ui_menu.py | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/bin/addons/base/ir/ir_ui_menu.py b/bin/addons/base/ir/ir_ui_menu.py index 9baff950c4f..99fc158042d 100644 --- a/bin/addons/base/ir/ir_ui_menu.py +++ b/bin/addons/base/ir/ir_ui_menu.py @@ -35,25 +35,6 @@ def one_in(setA, setB): return True return False -def cond(C, X, Y): - if C: return X - return Y - -class many2many_unique(fields.many2many): - def set(self, cr, obj, id, name, values, user=None, context=None): - if not values: - return - val = values[:] - for act in values: - if act[0]==4: - cr.execute('SELECT * FROM '+self._rel+' \ - WHERE '+self._id1+'=%s AND '+self._id2+'=%s', (id, act[1])) - if cr.fetchall(): - val.remove(act) - return super(many2many_unique, self).set(cr, obj, id, name, val, user=user, - context=context) - - class ir_ui_menu(osv.osv): _name = 'ir.ui.menu' @@ -298,7 +279,7 @@ class ir_ui_menu(osv.osv): 'sequence': fields.integer('Sequence'), 'child_id' : fields.one2many('ir.ui.menu', 'parent_id','Child IDs'), 'parent_id': fields.many2one('ir.ui.menu', 'Parent Menu', select=True), - 'groups_id': many2many_unique('res.groups', 'ir_ui_menu_group_rel', + 'groups_id': fields.many2many('res.groups', 'ir_ui_menu_group_rel', 'menu_id', 'gid', 'Groups', help="If you have groups, the visibility of this menu will be based on these groups. "\ "If this field is empty, OpenERP will compute visibility based on the related object's read access."), 'complete_name': fields.function(_get_full_name, method=True, From 91a0d108bf1207dca8eb442e80e92c93fae5590c Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Sun, 19 Dec 2010 22:08:42 +0200 Subject: [PATCH 06/13] workflow.instance: remove excess indices It is a table with 4 columns, doesn't worth 5 indices! I hope that these combined indexed plus the wkf_id one will allow Postgres to always come up with a good plan. bzr revid: p_christ@hol.gr-20101219200842-4vr72wqal2t8k2xq --- bin/addons/base/ir/workflow/workflow.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bin/addons/base/ir/workflow/workflow.py b/bin/addons/base/ir/workflow/workflow.py index e5bb3f25d67..c638855aacf 100644 --- a/bin/addons/base/ir/workflow/workflow.py +++ b/bin/addons/base/ir/workflow/workflow.py @@ -173,15 +173,15 @@ class wkf_instance(osv.osv): _log_access = False _columns = { 'wkf_id': fields.many2one('workflow', 'Workflow', ondelete='cascade', select=True), - 'res_id': fields.integer('Resource ID', select=True), - 'res_type': fields.char('Resource Object', size=64, select=True), - 'state': fields.char('State', size=32, select=True), + 'res_id': fields.integer('Resource ID'), + 'res_type': fields.char('Resource Object', size=64), + 'state': fields.char('State', size=32), } def _auto_init(self, cr, context=None): super(wkf_instance, self)._auto_init(cr, context) - cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'wkf_instance_res_id_res_type_state_index\'') + cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'wkf_instance_res_type_res_id_state_index\'') if not cr.fetchone(): - cr.execute('CREATE INDEX wkf_instance_res_id_res_type_state_index ON wkf_instance (res_id, res_type, state)') + cr.execute('CREATE INDEX wkf_instance_res_type_res_id_state_index ON wkf_instance (res_type, res_id, state)') cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'wkf_instance_res_id_wkf_id_index\'') if not cr.fetchone(): cr.execute('CREATE INDEX wkf_instance_res_id_wkf_id_index ON wkf_instance (res_id, wkf_id)') From aaac6a602f3836ae6f9c8919fcf6c1af5896ac51 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Sun, 19 Dec 2010 22:11:27 +0200 Subject: [PATCH 07/13] trml2pdf: Support EAN13 and QRcode barcodes, fix unicode warning [Bug 532559] Since ReportLab supports them, include it in our list, too. Also, fix the warning that has been appearing in buildbot, about "code128.py:257: UnicodeWarning ..." The issue is that string-capable barcodes will only support ASCII chars, not Unicode. So, cast to str() and let ReportLab's code be happy. Code was based on patch by: Omar (Pexego), on 22-06-2010 bzr revid: p_christ@hol.gr-20101219201127-mmrsn206a9vldr43 --- bin/report/render/rml2pdf/trml2pdf.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/bin/report/render/rml2pdf/trml2pdf.py b/bin/report/render/rml2pdf/trml2pdf.py index d01317f8257..497a4ba98c2 100644 --- a/bin/report/render/rml2pdf/trml2pdf.py +++ b/bin/report/render/rml2pdf/trml2pdf.py @@ -733,22 +733,26 @@ class _rml_flowable(object): from reportlab.graphics.barcode import common from reportlab.graphics.barcode import fourstate from reportlab.graphics.barcode import usps - except Exception, e: + from reportlab.graphics.barcode import createBarcodeDrawing + + except ImportError: self._logger.warning("Cannot use barcode renderers:", exc_info=True) return None args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'int','quiet':'int','width':'unit','stop':'bool','bearers':'int','barWidth':'float','barHeight':'float'}) codes = { 'codabar': lambda x: common.Codabar(x, **args), 'code11': lambda x: common.Code11(x, **args), - 'code128': lambda x: code128.Code128(x, **args), - 'standard39': lambda x: code39.Standard39(x, **args), - 'standard93': lambda x: code93.Standard93(x, **args), + 'code128': lambda x: code128.Code128(str(x), **args), + 'standard39': lambda x: code39.Standard39(str(x), **args), + 'standard93': lambda x: code93.Standard93(str(x), **args), 'i2of5': lambda x: common.I2of5(x, **args), - 'extended39': lambda x: code39.Extended39(x, **args), - 'extended93': lambda x: code93.Extended93(x, **args), + 'extended39': lambda x: code39.Extended39(str(x), **args), + 'extended93': lambda x: code93.Extended93(str(x), **args), 'msi': lambda x: common.MSI(x, **args), 'fim': lambda x: usps.FIM(x, **args), 'postnet': lambda x: usps.POSTNET(x, **args), + 'ean13': lambda x: createBarcodeDrawing('EAN13', value=str(x), **args), + 'qrcode': lambda x: createBarcodeDrawing('QR', value=x, **args), } code = 'code128' if node.get('code'): From 4236d93ba505d6771af5373d91ff8d7499622a3c Mon Sep 17 00:00:00 2001 From: "olt@tinyerp.com" <> Date: Mon, 20 Dec 2010 16:07:47 +0100 Subject: [PATCH 08/13] [FIX] tools.convert: logger.log: first argument needs to be an integer, not a string bzr revid: olt@tinyerp.com-20101220150747-4ull8wjwf7ichu9l --- bin/tools/convert.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/tools/convert.py b/bin/tools/convert.py index 49d56b15b1e..f7c86fa48e6 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -705,8 +705,8 @@ form: module.record_id""" % (xml_id,) ' expected count: %d\n' \ ' obtained count: %d\n' \ % (rec_string, count, len(ids)) - self.logger.log(severity, msg) sevval = getattr(logging, severity.upper()) + self.logger.log(sevval, msg) if sevval >= config['assert_exit_level']: # TODO: define a dedicated exception raise Exception('Severe assertion failure') @@ -737,8 +737,8 @@ form: module.record_id""" % (xml_id,) ' expected value: %r\n' \ ' obtained value: %r\n' \ % (rec_string, etree.tostring(test), expected_value, expression_value) - self.logger.log(severity, msg) sevval = getattr(logging, severity.upper()) + self.logger.log(sevval, msg) if sevval >= config['assert_exit_level']: # TODO: define a dedicated exception raise Exception('Severe assertion failure') From edcbf91f032a18af4b3e11633a831194897f6804 Mon Sep 17 00:00:00 2001 From: OpenERP Date: Mon, 20 Dec 2010 16:15:23 +0100 Subject: [PATCH 09/13] [FIX] orm, translate: unconditionally set the translation from .pot to db, lookup a translation by name, type, and lang only (no more source string) lp bug: https://launchpad.net/bugs/682092 fixed bzr revid: openerp@vmt-hp4520s-20101220151523-vy4xzvahz80tara7 --- bin/osv/orm.py | 2 +- bin/tools/translate.py | 61 ++++++++++-------------------------------- 2 files changed, 15 insertions(+), 48 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index a652ad54f04..73c91657d8c 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -1237,7 +1237,7 @@ class orm_template(object): res[f][arg] = getattr(field_col, arg) if field_col.string: - res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US', field_col.string) + res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US') if res_trans: res[f]['string'] = res_trans if field_col.help: diff --git a/bin/tools/translate.py b/bin/tools/translate.py index a296f0e445d..ff4dd4e3d9b 100644 --- a/bin/tools/translate.py +++ b/bin/tools/translate.py @@ -808,13 +808,13 @@ def trans_generate(lang, modules, dbname=None): cr.close() return out -def trans_load(db_name, filename, lang, strict=False, verbose=True, context=None): +def trans_load(db_name, filename, lang, verbose=True, context=None): logger = logging.getLogger('i18n') try: fileobj = open(filename,'r') logger.info("loading %s", filename) fileformat = os.path.splitext(filename)[-1][1:].lower() - r = trans_load_data(db_name, fileobj, fileformat, lang, strict=strict, verbose=verbose, context=context) + r = trans_load_data(db_name, fileobj, fileformat, lang, verbose=verbose, context=context) fileobj.close() return r except IOError: @@ -822,7 +822,7 @@ def trans_load(db_name, filename, lang, strict=False, verbose=True, context=None logger.error("couldn't read translation file %s", filename) return None -def trans_load_data(db_name, fileobj, fileformat, lang, strict=False, lang_name=None, verbose=True, context=None): +def trans_load_data(db_name, fileobj, fileformat, lang, lang_name=None, verbose=True, context=None): logger = logging.getLogger('i18n') if verbose: logger.info('loading translation file for language %s', lang) @@ -924,52 +924,19 @@ def trans_load_data(db_name, fileobj, fileformat, lang, strict=False, lang_name= else: dic['res_id'] = False - if dic['type'] == 'model' and not strict: - (model, field) = dic['name'].split(',') + args = [ + ('lang', '=', lang), + ('type', '=', dic['type']), + ('name', '=', dic['name']) + ] + if dic['type'] == 'model': + args.append(('res_id', '=', dic['res_id'])) - # get the ids of the resources of this model which share - # the same source - obj = pool.get(model) - if obj: - if field not in obj.fields_get_keys(cr, uid): - continue - # Using search() instead of _search() will limit the - # returned list to the ids visible to uid, which is not - # in the extended view group. We want all of them and use - # _search(). - ids = obj._search(cr, uid, [(field, '=', dic['src'])]) - - # if the resource id (res_id) is in that list, use it, - # otherwise use the whole list - if not ids: - ids = [] - ids = (dic['res_id'] in ids) and [dic['res_id']] or ids - for id in ids: - dic['res_id'] = id - ids = trans_obj.search(cr, uid, [ - ('lang', '=', lang), - ('type', '=', dic['type']), - ('name', '=', dic['name']), - ('src', '=', dic['src']), - ('res_id', '=', dic['res_id']) - ]) - if ids: - if context.get('overwrite', False): - trans_obj.write(cr, uid, ids, {'value': dic['value']}) - else: - trans_obj.create(cr, uid, dic) + ids = trans_obj.search(cr, uid, args) + if ids and context.get('overwrite', False): + trans_obj.write(cr, uid, ids, {'value': dic['value']}) else: - ids = trans_obj.search(cr, uid, [ - ('lang', '=', lang), - ('type', '=', dic['type']), - ('name', '=', dic['name']), - ('src', '=', dic['src']) - ]) - if ids: - if context.get('overwrite', False): - trans_obj.write(cr, uid, ids, {'value': dic['value']}) - else: - trans_obj.create(cr, uid, dic) + trans_obj.create(cr, uid, dic) cr.commit() cr.close() if verbose: From afed2d47fe33aa6f3f0aa1c3de500e26f8bf40da Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Mon, 20 Dec 2010 17:09:59 +0100 Subject: [PATCH 10/13] [FIX] fields.reference: don't accept half-defined references, and avoid crashing on previous bad ones lp bug: https://launchpad.net/bugs/604604 fixed bzr revid: odo@openerp.com-20101220160959-f6x2kik46btf0uol --- bin/osv/orm.py | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index aa5835414fd..44219344519 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -260,8 +260,11 @@ class browse_record(object): else: ref_obj, ref_id = result_line[field_name].split(',') ref_id = long(ref_id) - obj = self._table.pool.get(ref_obj) - new_data[field_name] = browse_record(self._cr, self._uid, ref_id, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) + if ref_id: + obj = self._table.pool.get(ref_obj) + new_data[field_name] = browse_record(self._cr, self._uid, ref_id, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) + else: + new_data[field_name] = browse_null() else: new_data[field_name] = browse_null() else: @@ -2436,6 +2439,29 @@ class orm(orm_template): if (val<>False) or (type(val)<>bool): cr.execute(update_query, (ss[1](val), key)) + def _check_selection_field_value(self, cr, uid, field, value, context=None): + """Raise except_orm if value is not among the valid values for the selection field""" + if self._columns[field]._type == 'reference': + val_model, val_id_str = value.split(',', 1) + val_id = False + try: + val_id = long(val_id_str) + except ValueError: + pass + if not val_id: + raise except_orm(_('ValidateError'), + _('Invalid value for reference field "%s" (last part must be a non-zero integer): "%s"') % (field, value)) + val = val_model + else: + val = value + if isinstance(self._columns[field].selection, (tuple, list)): + if val in dict(self._columns[field].selection): + return + elif val in dict(self._columns[field].selection(self, cr, uid, context=context)): + return + raise except_orm(_('ValidateError'), + _('The value "%s" for the field "%s" is not in the selection') % (value, field)) + def _check_removed_columns(self, cr, log=False): # iterate on the database columns to drop the NOT NULL constraints # of fields which were required but have been removed (or will be added by another module) @@ -3469,21 +3495,7 @@ class orm(orm_template): if field in self._columns \ and hasattr(self._columns[field], 'selection') \ and vals[field]: - if self._columns[field]._type == 'reference': - val = vals[field].split(',')[0] - else: - val = vals[field] - if isinstance(self._columns[field].selection, (tuple, list)): - if val not in dict(self._columns[field].selection): - raise except_orm(_('ValidateError'), - _('The value "%s" for the field "%s" is not in the selection') \ - % (vals[field], field)) - else: - if val not in dict(self._columns[field].selection( - self, cr, user, context=context)): - raise except_orm(_('ValidateError'), - _('The value "%s" for the field "%s" is not in the selection') \ - % (vals[field], field)) + self._check_selection_field_value(cr, user, field, vals[field], context=context) if self._log_access: upd0.append('write_uid=%s') From 0fedbb3215081590d6d7fc52bb29f56dedd271e1 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Mon, 20 Dec 2010 17:25:01 +0100 Subject: [PATCH 11/13] [FIX] fields.reference: don't accept half-defined references - second part of fix, in create() lp bug: https://launchpad.net/bugs/604604 fixed bzr revid: odo@openerp.com-20101220162501-v0xag7nwdhfij14f --- bin/osv/orm.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 44219344519..dfd1376d4a5 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -3739,21 +3739,7 @@ class orm(orm_template): if field in self._columns \ and hasattr(self._columns[field], 'selection') \ and vals[field]: - if self._columns[field]._type == 'reference': - val = vals[field].split(',')[0] - else: - val = vals[field] - if isinstance(self._columns[field].selection, (tuple, list)): - if val not in dict(self._columns[field].selection): - raise except_orm(_('ValidateError'), - _('The value "%s" for the field "%s" is not in the selection') \ - % (vals[field], field)) - else: - if val not in dict(self._columns[field].selection( - self, cr, user, context=context)): - raise except_orm(_('ValidateError'), - _('The value "%s" for the field "%s" is not in the selection') \ - % (vals[field], field)) + self._check_selection_field_value(cr, user, field, vals[field], context=context) if self._log_access: upd0 += ',create_uid,create_date' upd1 += ',%s,now()' From b56127a689081698decc1a78a3a7039bf8d25018 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 20 Dec 2010 17:46:48 +0100 Subject: [PATCH 12/13] [IMP] return the module version (installed_version) during web modules queries bzr revid: xmo@openerp.com-20101220164648-4n78ghbov1rruei5 --- bin/addons/base/module/module.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py index 1bdf2a7aebb..ba79dea4e13 100644 --- a/bin/addons/base/module/module.py +++ b/bin/addons/base/module/module.py @@ -522,13 +522,13 @@ class module(osv.osv): raise osv.except_osv(_('Error'), _('Module %s: Invalid Quality Certificate') % (mod.name,)) def list_web(self, cr, uid, context=None): - """ list_web(cr, uid, context) -> [module_name] + """ list_web(cr, uid, context) -> [(module_name, module_version)] Lists all the currently installed modules with a web component. - Returns a list of addon names. + Returns a list of a tuple of addon names and addon versions. """ return [ - module['name'] + (module['name'], module['installed_version']) for module in self.browse(cr, uid, self.search(cr, uid, [('web', '=', True), @@ -561,6 +561,7 @@ class module(osv.osv): 'to web client', names) return [ {'name': module.name, + 'version': module.installed_version, 'depends': list(self._web_dependencies( cr, uid, module, context=context)), 'content': addons.zip_directory( From a2055b6d3192c421c24230da96385c09a569a248 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Mon, 20 Dec 2010 20:17:24 +0100 Subject: [PATCH 13/13] [FIX] addons.zip_directory: ZipFile.write() still expects binary strings for filenames in python 2.5 lp bug: https://launchpad.net/bugs/690559 fixed bzr revid: odo@openerp.com-20101220191724-mvu1rsf3sup19s9m --- bin/addons/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index 18cf1890094..82bab896fbf 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -225,6 +225,10 @@ def zip_directory(directory, b64enc=True, src=True): archname = StringIO() archive = PyZipFile(archname, "w", ZIP_DEFLATED) + + # for Python 2.5, ZipFile.write() still expects 8-bit strings (2.6 converts to utf-8) + directory = tools.ustr(directory).encode('utf-8') + archive.writepy(directory) _zippy(archive, directory, src=src) archive.close()