diff --git a/bin/PKG-INFO b/bin/PKG-INFO index b13a196bcff..0cc1f0aa9fc 100644 --- a/bin/PKG-INFO +++ b/bin/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: OpenERP -Version: 5.0.4 +Version: 5.0.7 Author: Tiny.be Author-email: fp at tiny be Maintainer: Tiny.be diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index 96d23ea171a..3dbe722dc75 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -372,7 +372,10 @@ def init_module_objects(cr, module_name, obj_list): logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: creating or updating database tables' % module_name) todo = [] for obj in obj_list: - result = obj._auto_init(cr, {'module': module_name}) + try: + result = obj._auto_init(cr, {'module': module_name}) + except Exception, e: + raise if result: todo += result if hasattr(obj, 'init'): @@ -744,7 +747,6 @@ def load_modules(db, force_demo=False, status=None, update_module=False): if new_modules_in_graph == 0: # nothing to load break - logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list))) r = load_module_graph(cr, graph, status, report=report) has_updates = has_updates or r diff --git a/bin/addons/base/ir/ir.xml b/bin/addons/base/ir/ir.xml index a7b2aa81c37..c79083aee99 100644 --- a/bin/addons/base/ir/ir.xml +++ b/bin/addons/base/ir/ir.xml @@ -120,8 +120,8 @@ - @@ -1280,8 +1280,7 @@ - diff --git a/bin/addons/base/ir/ir_model.py b/bin/addons/base/ir/ir_model.py index c5361fd952d..d7250b3eecb 100644 --- a/bin/addons/base/ir/ir_model.py +++ b/bin/addons/base/ir/ir_model.py @@ -87,7 +87,7 @@ class ir_model(osv.osv): if vals.get('state','base')=='manual': self.instanciate(cr, user, vals['model'], context) self.pool.get(vals['model']).__init__(self.pool, cr) - self.pool.get(vals['model'])._auto_init(cr,{}) + self.pool.get(vals['model'])._auto_init(cr,{'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0')}) #pooler.restart_pool(cr.dbname) return res @@ -117,10 +117,10 @@ class ir_model_grid(osv.osv): result = super(osv.osv, self).read(cr, uid, ids, fields, context, load) allgr = self.pool.get('res.groups').search(cr, uid, [], context=context) acc_obj = self.pool.get('ir.model.access') - + if not isinstance(result,list): result=[result] - + for res in result: rules = acc_obj.search(cr, uid, [('model_id', '=', res['id'])]) rules_br = acc_obj.browse(cr, uid, rules, context=context) @@ -253,18 +253,19 @@ class ir_model_fields(osv.osv): vals['model'] = model_data.model if context and context.get('manual',False): vals['state'] = 'manual' - res = super(ir_model_fields,self).create(cr, user, vals, context) + res = super(ir_model_fields,self).create(cr, user, vals, context) if vals.get('state','base') == 'manual': if not vals['name'].startswith('x_'): raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !")) - + if 'relation' in vals and not self.pool.get('ir.model').search(cr, user, [('model','=',vals['relation'])]): raise except_orm(_('Error'), _("Model %s Does not Exist !" % vals['relation'])) - + if self.pool.get(vals['model']): self.pool.get(vals['model']).__init__(self.pool, cr) - self.pool.get(vals['model'])._auto_init(cr, {}) - + #Added context to _auto_init for special treatment to custom field for select_level + self.pool.get(vals['model'])._auto_init(cr, {'field_name':vals['name'],'field_state':'manual','select':vals.get('select_level','0')}) + return res ir_model_fields() @@ -464,7 +465,7 @@ class ir_model_data(osv.osv): if (not xml_id) and (not self.doinit): return False action_id = False - + if xml_id: cr.execute('select id,res_id from ir_model_data where module=%s and name=%s', (module,xml_id)) results = cr.fetchall() @@ -573,7 +574,7 @@ class ir_model_data(osv.osv): def _process_end(self, cr, uid, modules): if not modules: return True - modules = list(modules) + modules = list(modules) module_in = ",".join(["%s"] * len(modules)) cr.execute('select id,name,model,res_id,module from ir_model_data where module in (' + module_in + ') and noupdate=%s', modules + [False]) wkf_todo = [] diff --git a/bin/addons/base/ir/ir_rule.py b/bin/addons/base/ir/ir_rule.py index 99ba68168b9..cb4edeb55ff 100644 --- a/bin/addons/base/ir/ir_rule.py +++ b/bin/addons/base/ir/ir_rule.py @@ -110,11 +110,10 @@ class ir_rule(osv.osv): if rule.domain_force: res[rule.id] = eval(rule.domain_force, eval_user_data) else: - if rule.operand.startswith('user.') and rule.operand.count('.') > 1: + if rule.operand and rule.operand.startswith('user.') and rule.operand.count('.') > 1: #Need to check user.field.field1.field2(if field is False,it will break the chain) op = rule.operand[5:] rule.operand = rule.operand[:5+len(op[:op.find('.')])] +' and '+ rule.operand + ' or False' - if rule.operator in ('in', 'child_of'): dom = eval("[('%s', '%s', [%s])]" % (rule.field_id.name, rule.operator, eval(rule.operand,eval_user_data)), eval_user_data) diff --git a/bin/addons/base/ir/ir_values.py b/bin/addons/base/ir/ir_values.py index 355a1ac01d1..4c26596faa5 100644 --- a/bin/addons/base/ir/ir_values.py +++ b/bin/addons/base/ir/ir_values.py @@ -235,7 +235,7 @@ class ir_values(osv.osv): #ir_del(cr, uid, x[0]) return False else: - datas = pickle.loads(str(x[2])) + datas = pickle.loads(str(x[2].encode('utf-8'))) if meta: meta2 = pickle.loads(x[4]) return (x[0],x[1],datas,meta2) diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py index 49b6e7b6f2d..a6967773ac2 100644 --- a/bin/addons/base/module/module.py +++ b/bin/addons/base/module/module.py @@ -196,10 +196,18 @@ class module(osv.osv): return True if isinstance(ids, (int, long)): ids = [ids] - for mod in self.read(cr, uid, ids, ['state'], context): + mod_names = [] + for mod in self.read(cr, uid, ids, ['state','name'], context): if mod['state'] in ('installed', 'to upgrade', 'to remove', 'to install'): raise orm.except_orm(_('Error'), _('You try to remove a module that is installed or will be installed')) + mod_names.append(mod['name']) + #Removing the entry from ir_model_data + ids_meta = self.pool.get('ir.model.data').search(cr, uid, [('name', '=', 'module_meta_information'), ('module', 'in', mod_names)]) + + if ids_meta: + self.pool.get('ir.model.data').unlink(cr, uid, ids_meta, context) + return super(module, self).unlink(cr, uid, ids, context=context) def state_update(self, cr, uid, ids, newstate, states_to_update, context={}, level=100): diff --git a/bin/addons/base/res/partner/partner.py b/bin/addons/base/res/partner/partner.py index be5819ec79b..9c161af44de 100644 --- a/bin/addons/base/res/partner/partner.py +++ b/bin/addons/base/res/partner/partner.py @@ -115,7 +115,7 @@ def _partner_title_get(self, cr, uid, context={}): obj = self.pool.get('res.partner.title') ids = obj.search(cr, uid, [('domain', '=', 'partner')]) res = obj.read(cr, uid, ids, ['shortcut','name'], context) - return [(r['shortcut'], r['name']) for r in res] + return [(r['shortcut'], r['name']) for r in res] + [('','')] def _lang_get(self, cr, uid, context={}): obj = self.pool.get('res.lang') diff --git a/bin/addons/base/res/res_currency.py b/bin/addons/base/res/res_currency.py index 50a6679c46f..a34080e9756 100644 --- a/bin/addons/base/res/res_currency.py +++ b/bin/addons/base/res/res_currency.py @@ -82,7 +82,7 @@ class res_currency(osv.osv): if currency.rounding == 0: return 0.0 else: - return round(amount / currency.rounding) * currency.rounding + return round(amount / currency.rounding,6) * currency.rounding def is_zero(self, cr, uid, currency, amount): return abs(self.round(cr, uid, currency, amount)) < currency.rounding diff --git a/bin/addons/base/rng/view.rng b/bin/addons/base/rng/view.rng index e5db7ddf7f9..3a9c8f92369 100644 --- a/bin/addons/base/rng/view.rng +++ b/bin/addons/base/rng/view.rng @@ -344,6 +344,7 @@ + diff --git a/bin/netsvc.py b/bin/netsvc.py index 91630563452..5c2143535ef 100644 --- a/bin/netsvc.py +++ b/bin/netsvc.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################## # @@ -386,4 +387,186 @@ class OpenERPDispatcher: pdb.post_mortem(tb[2]) raise OpenERPDispatcherException(e, tb_s) +class GenericXMLRPCRequestHandler(OpenERPDispatcher): + def _dispatch(self, method, params): + try: + service_name = self.path.split("/")[-1] + return self.dispatch(service_name, method, params) + except OpenERPDispatcherException, e: + raise xmlrpclib.Fault(tools.exception_to_unicode(e.exception), e.traceback) + +class SSLSocket(object): + def __init__(self, socket): + if not hasattr(socket, 'sock_shutdown'): + from OpenSSL import SSL + ctx = SSL.Context(SSL.SSLv23_METHOD) + ctx.use_privatekey_file(tools.config['secure_pkey_file']) + ctx.use_certificate_file(tools.config['secure_cert_file']) + self.socket = SSL.Connection(ctx, socket) + else: + self.socket = socket + + def shutdown(self, how): + return self.socket.sock_shutdown(how) + + def __getattr__(self, name): + return getattr(self.socket, name) + +class SimpleXMLRPCRequestHandler(GenericXMLRPCRequestHandler, SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): + rpc_paths = map(lambda s: '/xmlrpc/%s' % s, GROUPS.get('web-services', {}).keys()) + +class SecureXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): + def setup(self): + self.connection = SSLSocket(self.request) + self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) + self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) + +class SimpleThreadedXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): + def server_bind(self): + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + SimpleXMLRPCServer.SimpleXMLRPCServer.server_bind(self) + +class SecureThreadedXMLRPCServer(SimpleThreadedXMLRPCServer): + def __init__(self, server_address, HandlerClass, logRequests=1): + SimpleThreadedXMLRPCServer.__init__(self, server_address, HandlerClass, logRequests) + self.socket = SSLSocket(socket.socket(self.address_family, self.socket_type)) + self.server_bind() + self.server_activate() + +class HttpDaemon(threading.Thread): + def __init__(self, interface, port, secure=False): + threading.Thread.__init__(self) + self.__port = port + self.__interface = interface + self.secure = bool(secure) + handler_class = (SimpleXMLRPCRequestHandler, SecureXMLRPCRequestHandler)[self.secure] + server_class = (SimpleThreadedXMLRPCServer, SecureThreadedXMLRPCServer)[self.secure] + + if self.secure: + from OpenSSL.SSL import Error as SSLError + else: + class SSLError(Exception): pass + try: + self.server = server_class((interface, port), handler_class, 0) + except SSLError, e: + Logger().notifyChannel('xml-rpc-ssl', LOG_CRITICAL, "Can not load the certificate and/or the private key files") + sys.exit(1) + except Exception, e: + Logger().notifyChannel('xml-rpc', LOG_CRITICAL, "Error occur when starting the server daemon: %s" % (e,)) + sys.exit(1) + + + def attach(self, path, gw): + pass + + def stop(self): + self.running = False + if os.name != 'nt': + try: + self.server.socket.shutdown( + hasattr(socket, 'SHUT_RDWR') and socket.SHUT_RDWR or 2) + except socket.error, e: + if e.errno != 57: raise + # OSX, socket shutdowns both sides if any side closes it + # causing an error 57 'Socket is not connected' on shutdown + # of the other side (or something), see + # http://bugs.python.org/issue4397 + Logger().notifyChannel( + 'server', LOG_DEBUG, + '"%s" when shutting down server socket, ' + 'this is normal under OS X'%e) + self.server.socket.close() + + def run(self): + self.server.register_introspection_functions() + + self.running = True + while self.running: + self.server.handle_request() + return True + + # If the server need to be run recursively + # + #signal.signal(signal.SIGALRM, self.my_handler) + #signal.alarm(6) + #while True: + # self.server.handle_request() + #signal.alarm(0) # Disable the alarm + +import tiny_socket +class TinySocketClientThread(threading.Thread, OpenERPDispatcher): + def __init__(self, sock, threads): + threading.Thread.__init__(self) + self.sock = sock + self.threads = threads + + def run(self): + import select + self.running = True + try: + ts = tiny_socket.mysocket(self.sock) + except: + self.sock.close() + self.threads.remove(self) + return False + while self.running: + try: + msg = ts.myreceive() + except: + self.sock.close() + self.threads.remove(self) + return False + try: + result = self.dispatch(msg[0], msg[1], msg[2:]) + ts.mysend(result) + except OpenERPDispatcherException, e: + new_e = Exception(tools.exception_to_unicode(e.exception)) # avoid problems of pickeling + ts.mysend(new_e, exception=True, traceback=e.traceback) + + self.sock.close() + self.threads.remove(self) + return True + + def stop(self): + self.running = False + + +class TinySocketServerThread(threading.Thread): + def __init__(self, interface, port, secure=False): + threading.Thread.__init__(self) + self.__port = port + self.__interface = interface + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.bind((self.__interface, self.__port)) + self.socket.listen(5) + self.threads = [] + + def run(self): + import select + try: + self.running = True + while self.running: + (clientsocket, address) = self.socket.accept() + ct = TinySocketClientThread(clientsocket, self.threads) + self.threads.append(ct) + ct.start() + self.socket.close() + except Exception, e: + self.socket.close() + return False + + def stop(self): + self.running = False + for t in self.threads: + t.stop() + try: + if hasattr(socket, 'SHUT_RDWR'): + self.socket.shutdown(socket.SHUT_RDWR) + else: + self.socket.shutdown(2) + self.socket.close() + except: + return False + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/osv/expression.py b/bin/osv/expression.py index 9b4b01d3fe9..d00b236239e 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -155,22 +155,21 @@ class expression(object): # values in the database, so we must ignore it : we generate a dummy leaf self.__exp[i] = self.__DUMMY_LEAF else: - subexp = field.search(cr, uid, table, left, [self.__exp[i]]) + subexp = field.search(cr, uid, table, left, [self.__exp[i]], context=context) # we assume that the expression is valid # we create a dummy leaf for forcing the parsing of the resulting expression self.__exp[i] = '&' self.__exp.insert(i + 1, self.__DUMMY_LEAF) for j, se in enumerate(subexp): self.__exp.insert(i + 2 + j, se) - - # else, the value of the field is store in the database, so we search on it + # else, the value of the field is store in the database, so we search on it elif field._type == 'one2many': # Applying recursivity on field(one2many) if operator == 'child_of': if isinstance(right, basestring): - ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], 'like', limit=None)] + ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], 'like', context=context, limit=None)] else: ids2 = list(right) if field._obj != working_table._name: @@ -184,7 +183,7 @@ class expression(object): if right: if isinstance(right, basestring): - ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator, limit=None)] + ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator, context=context, limit=None)] operator = 'in' else: if not isinstance(right,list): @@ -211,7 +210,7 @@ class expression(object): #FIXME if operator == 'child_of': if isinstance(right, basestring): - ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], 'like', limit=None)] + ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], 'like', context=context, limit=None)] else: ids2 = list(right) @@ -227,7 +226,7 @@ class expression(object): call_null_m2m = True if right: if isinstance(right, basestring): - res_ids = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator)] + res_ids = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator, context=context)] operator = 'in' else: if not isinstance(right, list): @@ -333,7 +332,7 @@ class expression(object): query = '(%s.%s in (%s))' % (table._table, left, right[0]) params = right[1] elif operator in ['in', 'not in']: - params = right[:] + params = right and right[:] or [] len_before = len(params) for i in range(len_before)[::-1]: if params[i] == False: @@ -342,14 +341,19 @@ class expression(object): len_after = len(params) check_nulls = len_after != len_before query = '(1=0)' - + if len_after: if left == 'id': instr = ','.join(['%s'] * len_after) else: instr = ','.join([table._columns[left]._symbol_set[0]] * len_after) query = '(%s.%s %s (%s))' % (table._table, left, operator, instr) - + else: + # the case for [field, 'in', []] or [left, 'not in', []] + if operator == 'in': + query = '(%s.%s IS NULL)' % (table._table, left) + else: + query = '(%s.%s IS NOT NULL)' % (table._table, left) if check_nulls: query = '(%s OR %s.%s IS NULL)' % (query, table._table, left) else: diff --git a/bin/osv/fields.py b/bin/osv/fields.py index f56f700596c..8af3f27417c 100644 --- a/bin/osv/fields.py +++ b/bin/osv/fields.py @@ -99,9 +99,9 @@ class _column(object): def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None): raise Exception(_('undefined get method !')) - def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None): - ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit) - res = obj.read(cr, uid, ids, [name]) + def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None): + ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit, context=context) + res = obj.read(cr, uid, ids, [name], context=context) return [x[name] for x in res] def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None): @@ -267,8 +267,8 @@ class one2one(_column): id = cr.fetchone()[0] obj.write(cr, user, [id], act[1], context=context) - def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None): - return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit) + def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None): + return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context) class many2one(_column): @@ -345,8 +345,8 @@ class many2one(_column): else: cr.execute('update '+obj_src._table+' set '+field+'=null where id=%s', (id,)) - def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None): - return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit) + def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None): + return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit, context=context) class one2many(_column): @@ -463,8 +463,8 @@ class one2many(_column): obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {}) return result - def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like'): - return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, offset, limit) + def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None): + return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit) # @@ -558,8 +558,8 @@ class many2many(_column): # # TODO: use a name_search # - def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like'): - return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit) + def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None): + return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit, context=context) def get_memory(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None): result = {} @@ -648,11 +648,11 @@ class function(_column): self._symbol_f = float._symbol_f self._symbol_set = float._symbol_set - def search(self, cr, uid, obj, name, args): + def search(self, cr, uid, obj, name, args, context=None): if not self._fnct_search: #CHECKME: should raise an exception return [] - return self._fnct_search(obj, cr, uid, obj, name, args) + return self._fnct_search(obj, cr, uid, obj, name, args, context=context) def get(self, cr, obj, ids, name, user=None, context=None, values=None): if not context: diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 648e8c87989..2d2cf799c3f 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -380,11 +380,19 @@ class orm_template(object): 'ttype': f._type, 'relation': f._obj or 'NULL', 'view_load': (f.view_load and 1) or 0, - 'select_level': str(f.select or 0), + 'select_level': tools.ustr(f.select or 0), 'readonly':(f.readonly and 1) or 0, 'required':(f.required and 1) or 0, 'selectable' : (f.selectable and 1) or 0, } + # When its a custom field,it does not contain f.select + if context.get('field_state','base') == 'manual': + if context.get('field_name','') == k: + vals['select_level'] = context.get('select','0') + #setting value to let the problem NOT occur next time + else: + vals['select_level'] = cols[k]['select_level'] + if k not in cols: cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',)) id = cr.fetchone()[0] @@ -862,6 +870,7 @@ class orm_template(object): except Exception, e: import psycopg2 import osv + cr.rollback() if isinstance(e,psycopg2.IntegrityError): msg= _('Insertion Failed! ') for key in self.pool._sql_error.keys(): @@ -1918,12 +1927,11 @@ class orm(orm_template): elif isinstance(f, fields.many2many): cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname=%s", (f._rel,)) if not cr.dictfetchall(): - #FIXME: Remove this try/except - try: - ref = self.pool.get(f._obj)._table - except AttributeError: - ref = f._obj.replace('.', '_') - cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL REFERENCES "%s" ON DELETE CASCADE, "%s" INTEGER NOT NULL REFERENCES "%s" ON DELETE CASCADE) WITHOUT OIDS' % (f._rel, f._id1, self._table, f._id2, ref)) + if not self.pool.get(f._obj): + raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,)) + ref = self.pool.get(f._obj)._table +# ref = f._obj.replace('.', '_') + cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL REFERENCES "%s" ON DELETE CASCADE, "%s" INTEGER NOT NULL REFERENCES "%s" ON DELETE CASCADE) WITH OIDS' % (f._rel, f._id1, self._table, f._id2, ref)) cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1)) cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2)) cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref)) @@ -1980,11 +1988,10 @@ class orm(orm_template): # and add constraints if needed if isinstance(f, fields.many2one): - #FIXME: Remove this try/except - try: - ref = self.pool.get(f._obj)._table - except AttributeError: - ref = f._obj.replace('.', '_') + if not self.pool.get(f._obj): + raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,)) + ref = self.pool.get(f._obj)._table +# ref = f._obj.replace('.', '_') # ir_actions is inherited so foreign key doesn't work on it if ref != 'ir_actions': cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (self._table, k, ref, f.ondelete)) @@ -2396,19 +2403,21 @@ class orm(orm_template): return 'length("%s") as "%s"' % (f, f) return '"%s"' % (f,) fields_pre2 = map(convert_field, fields_pre) + order_by = self._parent_order or self._order for i in range(0, len(ids), cr.IN_MAX): sub_ids = ids[i:i+cr.IN_MAX] if d1: cr.execute('SELECT %s FROM %s WHERE %s.id = ANY (%%s) AND %s ORDER BY %s' % \ (','.join(fields_pre2 + [self._table + '.id']), ','.join(tables), self._table, ' and '.join(d1), - self._order),[sub_ids,]+d2) + order_by),[sub_ids,]+d2) if not cr.rowcount == len({}.fromkeys(sub_ids)): raise except_orm(_('AccessError'), _('You try to bypass an access rule while reading (Document type: %s).') % self._description) else: cr.execute('SELECT %s FROM \"%s\" WHERE id = ANY (%%s) ORDER BY %s' % \ (','.join(fields_pre2 + ['id']), self._table, - self._order), (sub_ids,)) + ','.join(['%s' for x in sub_ids]), + order_by), sub_ids) res.extend(cr.dictfetchall()) else: res = map(lambda x: {'id': x}, ids) @@ -2729,8 +2738,12 @@ class orm(orm_template): # TODO: optimize for f in direct: if self._columns[f].translate: - src_trans = self.pool.get(self._name).read(cr,user,ids,[f]) - self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans[0][f]) + src_trans = self.pool.get(self._name).read(cr,user,ids,[f])[0][f] + if not src_trans: + src_trans = vals[f] + # Inserting value to DB + self.write(cr, user, ids, {f:vals[f]}) + self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f], src_trans) # call the 'set' method of fields which are not classic_write diff --git a/bin/release.py b/bin/release.py index 89ca3d54368..95801d58da1 100644 --- a/bin/release.py +++ b/bin/release.py @@ -21,7 +21,7 @@ ############################################################################## name = 'openerp-server' -version = '5.0.6' +version = '5.0.7' major_version = '5.0' description = 'OpenERP Server' long_desc = '''\ diff --git a/bin/report/custom.py b/bin/report/custom.py index 0b8aeb88bbe..e476b296056 100644 --- a/bin/report/custom.py +++ b/bin/report/custom.py @@ -290,7 +290,7 @@ class report_custom(report_int): def _append_node(name, text): n = etree.SubElement(config, name) - t.text = text + n.text = text _append_node('date', time.strftime('%d/%m/%Y')) _append_node('PageFormat', '%s' % report['print_format']) diff --git a/bin/report/render/odt2odt/__init__.py b/bin/report/render/odt2odt/__init__.py index 3ab97b95ed5..775cbabb3d4 100644 --- a/bin/report/render/odt2odt/__init__.py +++ b/bin/report/render/odt2odt/__init__.py @@ -19,4 +19,6 @@ # ############################################################################## -from odt2odt import parseNode \ No newline at end of file +from odt2odt import parseNode + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/bin/report/report_sxw.py b/bin/report/report_sxw.py index a03f8b4ba26..0cd4b229595 100644 --- a/bin/report/report_sxw.py +++ b/bin/report/report_sxw.py @@ -255,10 +255,10 @@ class rml_parse(object): parse_format = DHM_FORMAT # filtering time.strftime('%Y-%m-%d') - if type(value) == type(''): - parse_format = DHM_FORMAT - if (not date_time): - return str(value) +# if type(value) == type(''): +# parse_format = DHM_FORMAT +# if (not date_time): +# return str(value) if not isinstance(value, time.struct_time): try: @@ -361,8 +361,11 @@ class report_sxw(report_rml, preprocess.report): elif report_type=='mako2html': fnct = self.create_source_mako2html else: - raise Exception('Unknown Report Type: '+report_type) - return fnct(cr, uid, ids, data, report_xml, context) + raise 'Unknown Report Type' + fnct_ret = fnct(cr, uid, ids, data, report_xml, context) + if not fnct_ret: + return (False,False) + return fnct_ret def create_source_odt(self, cr, uid, ids, data, report_xml, context=None): return self.create_single_odt(cr, uid, ids, data, report_xml, context or {}) @@ -394,6 +397,8 @@ class report_sxw(report_rml, preprocess.report): results.append((d,'pdf')) continue result = self.create_single_pdf(cr, uid, [obj.id], data, report_xml, context) + if not result: + return False try: if aname: name = aname+'.'+result[1] @@ -431,6 +436,9 @@ class report_sxw(report_rml, preprocess.report): context = context.copy() title = report_xml.name rml = report_xml.report_rml_content + # if no rml file is found + if not rml: + return False rml_parser = self.parser(cr, uid, self.name2, context=context) objs = self.getObjects(cr, uid, ids, context) rml_parser.set_context(objs, data, ids, report_xml.report_type) diff --git a/bin/service/security.py b/bin/service/security.py index 23c67c9b7e1..ea87208c2dd 100644 --- a/bin/service/security.py +++ b/bin/service/security.py @@ -32,11 +32,10 @@ class ExceptionNoTb(Exception): self.args = (msg, '') def login(db, login, password): - cr = pooler.get_db(db).cursor() - if password: - cr.execute('select id from res_users where login=%s and password=%s and active', (tools.ustr(login), tools.ustr(password))) - else: - cr.execute('select id from res_users where login=%s and password is null and active', (tools.ustr(login),)) + if not password: + return False + cr = pooler.get_db(db).cursor() + cr.execute('select id from res_users where login=%s and password=%s and active', (tools.ustr(login), tools.ustr(password))) res = cr.fetchone() cr.close() if res: @@ -51,14 +50,13 @@ def check_super(passwd): raise ExceptionNoTb('AccessDenied') def check(db, uid, passwd): + if not passwd: + return False cached_pass = _uid_cache.get(db, {}).get(uid) if (cached_pass is not None) and cached_pass == passwd: return True - cr = pooler.get_db(db).cursor() - if passwd: - cr.execute('select count(1) from res_users where id=%s and password=%s and active=%s', (int(uid), passwd, True)) - else: - cr.execute('select count(1) from res_users where id=%s and password is null and active=%s', (int(uid), True)) + cr = pooler.get_db(db).cursor() + cr.execute('select count(1) from res_users where id=%s and password=%s and active=%s', (int(uid), passwd, True)) res = cr.fetchone()[0] cr.close() if not bool(res): @@ -72,11 +70,10 @@ def check(db, uid, passwd): return bool(res) def access(db, uid, passwd, sec_level, ids): - cr = pooler.get_db(db).cursor() - if passwd: - cr.execute('select id from res_users where id=%s and password=%s', (uid, passwd)) - else: - cr.execute('select id from res_users where id=%s and password is null', (uid,)) + if not passwd: + return False + cr = pooler.get_db(db).cursor() + cr.execute('select id from res_users where id=%s and password=%s', (uid, passwd)) res = cr.fetchone() cr.close() if not res: diff --git a/bin/service/web_services.py b/bin/service/web_services.py index 85b7056aa8d..0022e15bb8d 100644 --- a/bin/service/web_services.py +++ b/bin/service/web_services.py @@ -78,13 +78,16 @@ class db(netsvc.ExportService): self.actions[id] = {'clean': False} db = sql_db.db_connect('template1') - cr = db.cursor() + db.lock() try: - cr.autocommit(True) # avoid transaction block - cr.execute('CREATE DATABASE "%s" ENCODING \'unicode\'' % db_name) + cr = db.cursor() + try: + cr.autocommit(True) # avoid transaction block + cr.execute('CREATE DATABASE "%s" ENCODING \'unicode\'' % db_name) + finally: + cr.close() finally: - cr.close() - del db + db.release() class DBInitialize(object): def __call__(self, serv, id, db_name, demo, lang, user_password='admin'): @@ -158,20 +161,24 @@ class db(netsvc.ExportService): logger = netsvc.Logger() db = sql_db.db_connect('template1') - cr = db.cursor() - cr.autocommit(True) # avoid transaction block + db.lock() try: + cr = db.cursor() + cr.autocommit(True) # avoid transaction block try: - cr.execute('DROP DATABASE "%s"' % db_name) - except Exception, e: - logger.notifyChannel("web-services", netsvc.LOG_ERROR, - 'DROP DB: %s failed:\n%s' % (db_name, e)) - raise Exception("Couldn't drop database %s: %s" % (db_name, e)) - else: - logger.notifyChannel("web-services", netsvc.LOG_INFO, - 'DROP DB: %s' % (db_name)) + try: + cr.execute('DROP DATABASE "%s"' % db_name) + except Exception, e: + logger.notifyChannel("web-services", netsvc.LOG_ERROR, + 'DROP DB: %s failed:\n%s' % (db_name, e)) + raise Exception("Couldn't drop database %s: %s" % (db_name, e)) + else: + logger.notifyChannel("web-services", netsvc.LOG_INFO, + 'DROP DB: %s' % (db_name)) + finally: + cr.close() finally: - cr.close() + db.release() return True def _set_pg_psw_env_var(self): @@ -223,13 +230,16 @@ class db(netsvc.ExportService): raise Exception, "Database already exists" db = sql_db.db_connect('template1') - cr = db.cursor() - cr.autocommit(True) # avoid transaction block + db.lock() try: - cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "template0" """ % db_name) + cr = db.cursor() + cr.autocommit(True) # avoid transaction block + try: + cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "template0" """ % db_name) + finally: + cr.close() finally: - cr.close() - del db + db.release() cmd = ['pg_restore', '--no-owner'] if tools.config['db_user']: @@ -267,23 +277,27 @@ class db(netsvc.ExportService): logger = netsvc.Logger() db = sql_db.db_connect('template1') - cr = db.cursor() + db.lock() try: + cr = db.cursor() try: - cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name)) - except Exception, e: - logger.notifyChannel("web-services", netsvc.LOG_ERROR, - 'RENAME DB: %s -> %s failed:\n%s' % (old_name, new_name, e)) - raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e)) - else: - fs = os.path.join(tools.config['root_path'], 'filestore') - if os.path.exists(os.path.join(fs, old_name)): - os.rename(os.path.join(fs, old_name), os.path.join(fs, new_name)) + try: + cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name)) + except Exception, e: + logger.notifyChannel("web-services", netsvc.LOG_ERROR, + 'RENAME DB: %s -> %s failed:\n%s' % (old_name, new_name, e)) + raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e)) + else: + fs = os.path.join(tools.config['root_path'], 'filestore') + if os.path.exists(os.path.join(fs, old_name)): + os.rename(os.path.join(fs, old_name), os.path.join(fs, new_name)) - logger.notifyChannel("web-services", netsvc.LOG_INFO, - 'RENAME DB: %s -> %s' % (old_name, new_name)) + logger.notifyChannel("web-services", netsvc.LOG_INFO, + 'RENAME DB: %s -> %s' % (old_name, new_name)) + finally: + cr.close() finally: - cr.close() + db.release() return True def exp_db_exist(self, db_name): @@ -291,30 +305,34 @@ class db(netsvc.ExportService): return bool(sql_db.db_connect(db_name)) def exp_list(self): + if not tools.config['list_db']: + raise Exception('AccessDenied') + db = sql_db.db_connect('template1') - cr = db.cursor() + db.lock() try: - list_db = tools.config["list_db"] - if list_db == 'False': - return [] + cr = db.cursor() try: - db_user = tools.config["db_user"] - if not db_user and os.name == 'posix': - import pwd - db_user = pwd.getpwuid(os.getuid())[0] - if not db_user: - cr.execute("select decode(usename, 'escape') from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (tools.config["db_name"],)) - res = cr.fetchone() - db_user = res and str(res[0]) - if db_user: - cr.execute("select decode(datname, 'escape') from pg_database where datdba=(select usesysid from pg_user where usename=%s) and datname not in ('template0', 'template1', 'postgres') order by datname", (db_user,)) - else: - cr.execute("select decode(datname, 'escape') from pg_database where datname not in('template0', 'template1','postgres') order by datname") - res = [str(name) for (name,) in cr.fetchall()] - except: - res = [] + try: + db_user = tools.config["db_user"] + if not db_user and os.name == 'posix': + import pwd + db_user = pwd.getpwuid(os.getuid())[0] + if not db_user: + cr.execute("select decode(usename, 'escape') from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (tools.config["db_name"],)) + res = cr.fetchone() + db_user = res and str(res[0]) + if db_user: + cr.execute("select decode(datname, 'escape') from pg_database where datdba=(select usesysid from pg_user where usename=%s) and datname not in ('template0', 'template1', 'postgres') order by datname", (db_user,)) + else: + cr.execute("select decode(datname, 'escape') from pg_database where datname not in('template0', 'template1','postgres') order by datname") + res = [str(name) for (name,) in cr.fetchall()] + except: + res = [] + finally: + cr.close() finally: - cr.close() + db.release() res.sort() return res @@ -709,15 +727,19 @@ class report_spool(netsvc.ExportService): def go(id, uid, ids, datas, context): cr = pooler.get_db(db).cursor() + import traceback + import sys try: obj = netsvc.LocalService('report.'+object) (result, format) = obj.create(cr, uid, ids, datas, context) + if not result: + tb = sys.exc_info() + self._reports[id]['exception'] = ExceptionWithTraceback('RML is not available at specified location or not enough data to print!', tb) self._reports[id]['result'] = result self._reports[id]['format'] = format self._reports[id]['state'] = True except Exception, exception: - import traceback - import sys + tb = sys.exc_info() tb_s = "".join(traceback.format_exception(*tb)) logger = netsvc.Logger() diff --git a/bin/sql_db.py b/bin/sql_db.py index b4ce8347f43..dc462526049 100644 --- a/bin/sql_db.py +++ b/bin/sql_db.py @@ -299,17 +299,31 @@ class Connection(object): self.dbname = dbname self._pool = pool self._unique = unique - if unique: - if dbname not in self.__LOCKS: - self.__LOCKS[dbname] = threading.Lock() - self.__LOCKS[dbname].acquire() - def __del__(self): + def __enter__(self): if self._unique: - close_db(self.dbname) - self.__LOCKS[self.dbname].release() + self.lock() + return self + + def __exit__(self, exc_type, exc_value, traceback): + if self._unique: + self.release() + + def lock(self): + if self.dbname not in self.__LOCKS: + self.__LOCKS[self.dbname] = threading.Lock() + self.__LOCKS[self.dbname].acquire() + + def release(self): + close_db(self.dbname) + self.__LOCKS[self.dbname].release() def cursor(self, serialized=False): + if self._unique: + lock = self.__LOCKS.get(self.dbname, None) + if not (lock and lock.locked()): + netsvc.Logger().notifyChannel('Connection', netsvc.LOG_WARNING, 'Unprotected connection to %s' % (self.dbname,)) + return Cursor(self._pool, self.dbname, serialized=serialized) def serialized_cursor(self): diff --git a/bin/tools/config.py b/bin/tools/config.py index e0ef74dbb56..8abc3a317b4 100644 --- a/bin/tools/config.py +++ b/bin/tools/config.py @@ -121,6 +121,8 @@ class configmanager(object): parser.add_option("--assert-exit-level", dest='assert_exit_level', type="choice", choices=self._LOGLEVELS.keys(), help="specify the level at which a failed assertion will stop the server. Accepted values: %s" % (self._LOGLEVELS.keys(),)) parser.add_option('--price_accuracy', dest='price_accuracy', default='2', help='specify the price accuracy') + parser.add_option('--no-database-list', action="store_false", dest='list_db', default=True, help="disable the ability to return the list of databases") + if hasSSL: group = optparse.OptionGroup(parser, "SSL Configuration") group.add_option("-S", "--secure", dest="secure", @@ -240,7 +242,10 @@ class configmanager(object): self.options[arg] = getattr(opt, arg) keys = ['language', 'translate_out', 'translate_in', 'debug_mode', - 'stop_after_init', 'logrotate'] + 'stop_after_init', 'logrotate', 'without_demo', 'netrpc', 'xmlrpc', 'syslog', 'list_db'] + + if hasSSL and not self.options['secure']: + keys.append('secure') for arg in keys: if getattr(opt, arg) is not None: diff --git a/bin/tools/misc.py b/bin/tools/misc.py index a237ee1b603..2984fb09186 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -636,9 +636,6 @@ class UpdateableDict(local): def __ge__(self, y): return self.dict.__ge__(y) - def __getitem__(self, y): - return self.dict.__getitem__(y) - def __gt__(self, y): return self.dict.__gt__(y) diff --git a/bin/tools/translate.py b/bin/tools/translate.py index fdeffaa09f0..f5ba70990cf 100644 --- a/bin/tools/translate.py +++ b/bin/tools/translate.py @@ -154,8 +154,12 @@ _ = GettextAlias() # class to handle po files class TinyPoFile(object): def __init__(self, buffer): + self.logger = netsvc.Logger() self.buffer = buffer + def warn(self, msg): + self.logger.notifyChannel("i18n", netsvc.LOG_WARNING, msg) + def __iter__(self): self.buffer.seek(0) self.lines = self._get_lines() @@ -218,7 +222,6 @@ class TinyPoFile(object): # This has been a deprecated entry, don't return anything return self.next() - if not line.startswith('msgid'): raise Exception("malformed file: bad line: %s" % line) source = unquote(line[6:]) @@ -250,8 +253,9 @@ class TinyPoFile(object): self.tnrs.append((t, n, r, source, trad)) self.first = False - - if name == None: + + if name is None: + self.warn('Missing "#:" formated comment for the following source:\n\t%s' % (source,)) return self.next() return type, name, res_id, source, trad diff --git a/doc/Changelog b/doc/Changelog index e8866ff37b1..85f3271ebc6 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,335 @@ +2009-12-03: 5.0.7 +================= + +Bugfixes (server) +----------------- + + * Not linked to a bug report: + * translation: modify msg id where it contains python code in translations po files + * use env for python shebang rather than hardcoding /usr/bin/python + * base: wrong fr_FR translation for 'Delete Permission' + * db_exist method works as expected + * quality_integration_server : fixed bug on make link + * quality_integration_server: wrong web-service name + * Upgrade could have failed when its a change in name field,added cascade + * Expression : Making search easier for 'child_of'- Recursivity on field used as a Left operand + * solve problem of importing data from csv with constraints available on object + * act_window False domain problem - produce after dom to lxml conversion + * On update, fields with only numeric/int/float could have digits + * On Update, if only the size of column is changed,it should reflect into DB + * quality_integration_server : fixed problem in integration server on translation checking if translation file is changed on base module + * Avoid crash when no args + * Config : wrong calculation if cached_timeout comes from config file + * Expression : domain calculation failes, max. recursion error protected + * recursive calls + * allow to call write without ids on osv_memory objects + * Specify the name of the argument for the context, to avoir a critical bug + * problem Report Engine + * Workflow button desactivation regarding user roles + * Improved Previous bad commit for pagelayout error + * Import : Context of the action/screen passed,taken into consideration while importing + * quality_integration_server: ascci encoding problem in quality html log + * recursive child_of on one2many and many2many + * default context + * https://launchpad.net/bugs/463415 + * https://launchpad.net/bugs/490604 + * RAW reports creation corrected + * https://launchpad.net/bugs/430133 + * Name_search() is having now record limit to be 80 instead of None + * https://launchpad.net/bugs/434341 + * Export : Selection field gets imported by its external name if export is not import compatible + * https://launchpad.net/bugs/489355 + * fields: '_fnct_write' should pass context object to the 'write' method + * https://launchpad.net/bugs/410191 + * Record rule : domain evaluation problem corrected + * https://launchpad.net/bugs/420507 + * Domain was getting failed when trying to work upon M2M,O2M field of object + * https://launchpad.net/bugs/453269 + * Amount to text conversions made better + * https://launchpad.net/bugs/488234 + * connection pool when database password is provided (thanks to Syleam Crew) + * https://launchpad.net/bugs/430805 + * Import made successful when field is O2M and it has relation under that. + * https://launchpad.net/bugs/480301 + * M2M : values filtering on set() + * https://launchpad.net/bugs/462506 + * Priority on fields.function with store dictionary made working. + * https://launchpad.net/bugs/433886 + * Update Module : Float8 to float and numeric to float casting made possible + * https://launchpad.net/bugs/491365 + * Translation: Error parsing .po files when application puts comments in a single line + * https://launchpad.net/bugs/460560 + * Ir_attachment : Context updation corrected on preview + * https://launchpad.net/bugs/356628 + * allow related fields to point to one2many and many2many fields + * https://launchpad.net/bugs/483527 + * https://launchpad.net/bugs/435933 + * Encoding error corrected for client_action(ir_values-tree view) + * https://launchpad.net/bugs/430728 + * Allowing sql keywords as fields(don't use them in order by clause) + * https://launchpad.net/bugs/487836 + * Custom Object xml arch needed encoding. + * https://launchpad.net/bugs/487723 + * Module informtion was not getting updated on upgrading module + * https://launchpad.net/bugs/480782 + * https://launchpad.net/bugs/478724 + * Export : M2M field name was missing last character + * https://launchpad.net/bugs/437729 + * Export Translation : Warning on Non-existing record instead of breaking flow + * https://launchpad.net/bugs/429519 + * https://launchpad.net/bugs/433395 + * https://launchpad.net/bugs/479915 + * Added lxml as required module on setup + * https://launchpad.net/bugs/400614 + * Use an alternative for the locale.RADIXCHAR if this one doesn't exist + * https://launchpad.net/bugs/491462 + * Pickling issue solved with ir_values (get method) + + +Improvements (server) +--------------------- + + * add option to disable the database listing + * sql_log + * quality_integration_server: added net_port option in script + * better connection pool (global) + * ir_cron : added active feild on list view + * Allow to specify the view via the context + * cron: check the arguments to avoid security issues + * cron: failed jobs are logged + * cron: clean code + * base_quality_interrrogation: put message if the score of quality less then minimal and remove unit test condition + * quality_integration_server: quality log : add new option to specify qualitylog path to store html pages of log + * default value for module + + +Bugfixes (addons) +----------------- + + * Not linked to a bug report: + * Project : remove unused import causing a DeprecationWarning under Python 2.6 + * l10n_be: corrected internal type of vat account in l10n_be: set other instead of payable/receivable + * Hr_timesheet_sheet : setting type=workflow to 2 buttons + * Account: text made translatable + * Stock : Added Product UoS to the Stock Move view + * fix the problem of menus uring installation + * Base_contact : Making Email field of address visible on Address form view + * Stock : Added translations + * Account_invoice_layout : Reports improved and SXWs made compatible to RML + * membership: invoice membership wizard now calculate tax corectly and on product membership fields make readonly to false + * membership: change membership product date from and date to value with current year on demo data + * membership: solve problem of deleting membership invoice created with old membership product + * Account: Wizards were missing translation import statement + * Residual amount into invoice is now correct in every case and avoid rounding trouble even if rating has changed + * account: avoid translate tool missing import on strictier Python 2.6 versions; bare in mind that mx.Datetime should be eraticated from the surface of the earth + * account: removed pdb now that things are claimed fixed (after the commits messages) + * account_analytic_plans: Use etree instead of xml.dom + * fix the problem that appers in to the account move due to accout voucher + * Account_date_check : correction on a method + * Account_voucher : Typo corrected for Voucher type. + * Account : Corrected the malformed report of Partner Ledger + * purchase_manual: Import OSV + * python2.6 compatibility + * AuditTrail : View Logs should only show logs of current object and resources + * fix the problem of the reports account voucher + * #TODO: fix me sale module can not be used here, + * Import OSV in the wizard + * purchase: Use the price_accuracy + * Base_Report_Creator : xml record was malformed, corrected + * hr_expense: corrected error message + * account: avoid ZeroDivisionError + * Specify the name of the argument for the context, to avoir a critical bug + * CRM : Mailgate script added option -he was conflicting,improved + * Account :Invoice report had Partner name displayed at wrong position + * bugfix residual computation and multi-currencies + * Base_report_creator : Calendar view problems on custom report + * typo + * workcenter load graph + * set the access right + * base_module_quality: speed test if read method has exception + * account_analytic_plans: avoid encoding errors + * https://launchpad.net/bugs/458415 + * document_ics : solved accent problem + * https://launchpad.net/bugs/447402 + * Project_timesheet : Wrong synchro on analytic line creation fro tasks(for name). + * https://launchpad.net/bugs/490318 + * Account : Ondelete=cascade added to bank statement line + * https://launchpad.net/bugs/446520 + * [account_followup] wrong sender for email + * https://launchpad.net/bugs/454536 + * Warning : Onchange Methods were malfunctioned.Corrected and made messages available for translations + * https://launchpad.net/bugs/450180 + * Hr_timesheet_invoice : Wrong domain was sent for opening invoices created + * https://launchpad.net/bugs/383057 + * Stock-MRP : Split production wizard made individual to mrp if mrp is not installed + * https://launchpad.net/bugs/488869 + * Stock/MRP : Track line wizard improved + * https://launchpad.net/bugs/446681 + * Account : Refund wuzard malfunctioned with modify invoice option + * https://launchpad.net/bugs/446391 + * Base_report_creator : Allowing to use current userid + * https://launchpad.net/bugs/479747 + * https://launchpad.net/bugs/470359 + * https://launchpad.net/bugs/460701 + * Document : Attachment with document can now be deleted + * https://launchpad.net/bugs/439469 + * Product : Pricelist types getting translated on Pricelist + * https://launchpad.net/bugs/489355 + * account, invoice, sale: description (product sold, invoice line and account entry line) was not translated + * https://launchpad.net/bugs/435160 + * [IMP] Correct write-off date, add analytic account, better interface between all way to reconcile + * https://launchpad.net/bugs/481524 + * Stock : Forecast report: unicode error corrected + * https://launchpad.net/bugs/458553 + * Account : Onchange of amount/base amount of Invocie tax corrected + * https://launchpad.net/bugs/438705 + * Stock : Stock move lines on Production Order well-structured + * https://launchpad.net/bugs/441609 + * Account : Fiscal Position Template was missing a requireed field in form view + * https://launchpad.net/bugs/467880 + * MRP : Procurement can only be confirmed when qty>0.0 + * https://launchpad.net/bugs/486783 + * Point_of_sale : Report for Reciept corrected + * https://launchpad.net/bugs/448591 + * Account : Refund wizard wasnt calculating taxes for new invoices + * https://launchpad.net/bugs/395160 + * Project_timesheet : Analytic line creation/edition takes user based information + * https://launchpad.net/bugs/436008 + * Sale: Passing Contact address for invoices from SO + * https://launchpad.net/bugs/428926 + * account_payment when importing payment lines (currency not set correctly) + * https://launchpad.net/bugs/439041 + * Report_project :wrong average of closing days counting + * https://launchpad.net/bugs/396637 + * account_analytic_analysis : Analytic account functional field methods corrected. + * Account : Analytic account functional field methods corrected. + * https://launchpad.net/bugs/445547 + * Stock: Picking report correction + * https://launchpad.net/bugs/443069 + * Project: wrong domain for 'Tasks in Progress' menuitem + * https://launchpad.net/bugs/483723 + * CRM : Case form view priority issue resolved + * https://launchpad.net/bugs/427869 + * Residual amount in invoice when currency rating change + * https://launchpad.net/bugs/416810 + * Document: attachment linked with Files + * https://launchpad.net/bugs/461801 + * Sale : Workflow behaviour fixed when order is set to draft + * https://launchpad.net/bugs/461720 + * Scrum : Wrong view,widgetless fields on scrum view made correct + * https://launchpad.net/bugs/474337 + * [CRITICAL] warning : missing import statement for "_" + * https://launchpad.net/bugs/443132 + * Sale : Passing customer ref. of picking to invoice + * https://launchpad.net/bugs/488809 + * Sale : State was written wrongly + * https://launchpad.net/bugs/465010 + * Stock : Notification Message made translatable + * https://launchpad.net/bugs/490342 + * Account : making the default_get() eTiny compatible + * https://launchpad.net/bugs/453030 + * Avoid display write-off in pay invoice wizard : take care of partial payment + * Avoid display write-off in pay invoice wizard. Put the right date and currency for conversion + * https://launchpad.net/bugs/445267 + * Mrp_subproduct : Wrong calculation of QTYs corrected(Product qty,UOS qty) + * https://launchpad.net/bugs/401035 + * Audittrail : Assigned Access Rights to non-admin user. + * https://launchpad.net/bugs/421636 + * Account : Restricting Payment term lines percentage insertion from 0 to 1 + * https://launchpad.net/bugs/425671 + * Stock : Moves offer onchange on UOM to affect UOS + * https://launchpad.net/bugs/489083 + * Account :customer refund was displaying supplier invoice view, corrected + * https://launchpad.net/bugs/480856 + * Warning : the super of onchange may return {},covered the same + * https://launchpad.net/bugs/459196 + * Account : Partner Ledger Report formatting problem solved + * https://launchpad.net/bugs/483583 + * Sale/Purcghase : Function fields did not have digits attribute for precision accuracy + * https://launchpad.net/bugs/475135 + * Account : Warning message was missing _ import + * https://launchpad.net/bugs/457188 + * Account_analytic_analysis : Summary of Months calculation Corrected + * https://launchpad.net/bugs/435178 + * [CRITICAL]subscription: crash subscription process + * https://launchpad.net/bugs/480035 + * CRM : action name improved + * https://launchpad.net/bugs/487091 + * Stock :Invoice created from manual picking might miss UoS. + * https://launchpad.net/bugs/435298 + * Subscription : Disallowed to change the Object linked to the document type. + * https://launchpad.net/bugs/436651 + * mrp : workcentre load report had a query malformed + * https://launchpad.net/bugs/479886 + * Account : Total field on supplier invoice set to 0.0 to amke it Web-client compatible + * https://launchpad.net/bugs/479195 + * Base_vat : Romania VAT validation corrected + * https://launchpad.net/bugs/351083 + * Account : Partner Balance report gets printed with respect to the company selected + * https://launchpad.net/bugs/451310 + * https://launchpad.net/bugs/449583 + * Sale : Better error message when account is missing from SOL for Invioce + * https://launchpad.net/bugs/486794 + * Hr_holidays: Spell mistake corrected + * https://launchpad.net/bugs/351167 + * https://launchpad.net/bugs/438725 + * Purchase : Setting the value of payment term while creatying invoice from PO + * https://launchpad.net/bugs/471052 + * Product : Pricelist Item cannot use Main pricelist as the other pricelist + * https://launchpad.net/bugs/481372 + * Project : If company has no Project time unit,it would have crashed. + * https://launchpad.net/bugs/458030 + * Account : Ledger Report Landscape report adjusted for A4. + * https://launchpad.net/bugs/446205 + * CRM : Mailgate script has host as parameter now onwards + * https://launchpad.net/bugs/487641 + * Purchase : MOQ-pricing problem corrected + * https://launchpad.net/bugs/476428 + * Stock : Partial Picking wizard was missing translation import statement + * https://launchpad.net/bugs/366944 + * Base_vat : Spanish numbers validation corrected + * https://launchpad.net/bugs/466658 + * Account : Action window of anlytic entries was missing name + * https://launchpad.net/bugs/440557 + * Purchase : POL had a wrong tree view which is unused till now + * https://launchpad.net/bugs/452854 + * Correct the validate function for balanced move into account.py (after Fabien Warning) + * Use price_accuracy to verify balanced entry insteed of fixed '0,0001'. Add price_accuracy on debit and credit move lines + * https://launchpad.net/bugs/491241 + * Stock : Removal of picking shuold affect product stock + * https://launchpad.net/bugs/490327 + * Reverted bad commit from Joel@CamptoCamp + * https://launchpad.net/bugs/440734 + * Stock : Picking did not have 'type' field on display(inconsistent behavior from eTiny,koo) to work upon domain. + * https://launchpad.net/bugs/436174 + * Account: Supplier invoices was not taking product price on onchange of product + * https://launchpad.net/bugs/474340 + * Stock : Wizard improvements from Lionel + * https://launchpad.net/bugs/440711 + * Purchase : PO with different pricelists should not be merged + * https://launchpad.net/bugs/439208 + * Hr_timesheet : Allowing user to enter Analytic Journal if not linked with employee(Working hours tree view) + * https://launchpad.net/bugs/379118 + * Stock : Partial Picking wizard was making the back order reference jump to 2 numbers,notification given on packing done. + * https://launchpad.net/bugs/476343 + * https://launchpad.net/bugs/419720 + * Sale : Delivery Date delay computation made corrected when days are passed with fractions + + +Improvements (addons) +--------------------- + + * MRP : mrp.routing.workcenter made deletable on cascade effect + * Account : Entry Line action Name Improved + * Stock : Improved names of Stock move tree view + * account: make comment mandatory. Since it has a default value anyway, it doesn't reall y change anything, but makes the reconciliation UI looks consistent accross the whole OpenERP (eg invoice payment) + * Add support of analytic account into bank statement to be convenient with other method + * Add account field into bank statement line + * better python2.5/2.6 compatibility handling + * base_module_quality: styles on different tests + * base_module_quality: modify all tests result display for buildpot, use class on tag instead of css file + * base_module_quality: pylint test result display for buildpot, use class on tag instead of css file + 2009-09-22: 5.0.6 =================