diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py
index 025b2a7b772..395cf573c0a 100644
--- a/bin/addons/__init__.py
+++ b/bin/addons/__init__.py
@@ -45,11 +45,17 @@ from cStringIO import StringIO
logger = netsvc.Logger()
_ad = os.path.abspath(opj(tools.config['root_path'], 'addons')) # default addons path (base)
-ad = os.path.abspath(tools.config['addons_path']) # alternate addons path
+ad_paths= map(lambda m: os.path.abspath(m.strip()),tools.config['addons_path'].split(','))
sys.path.insert(1, _ad)
-if ad != _ad:
- sys.path.insert(1, ad)
+
+ad_cnt=1
+for adp in ad_paths:
+ if adp != _ad:
+ sys.path.insert(ad_cnt, adp)
+ ad_cnt+=1
+
+ad_paths.append(_ad) # for get_module_path
# Modules already loaded
loaded = []
@@ -153,11 +159,10 @@ class Node(Singleton):
def get_module_path(module, downloaded=False):
"""Return the path of the given module."""
- if os.path.exists(opj(ad, module)) or os.path.exists(opj(ad, '%s.zip' % module)):
- return opj(ad, module)
+ for adp in ad_paths:
+ if os.path.exists(opj(adp, module)) or os.path.exists(opj(adp, '%s.zip' % module)):
+ return opj(adp, module)
- if os.path.exists(opj(_ad, module)) or os.path.exists(opj(_ad, '%s.zip' % module)):
- return opj(_ad, module)
if downloaded:
return opj(_ad, module)
logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: module not found' % (module,))
@@ -282,7 +287,10 @@ def get_modules():
return os.path.isdir(name) or zipfile.is_zipfile(name)
return map(clean, filter(is_really_module, os.listdir(dir)))
- return list(set(listdir(ad) + listdir(_ad)))
+ plist = []
+ for ad in ad_paths:
+ plist.extend(listdir(ad))
+ return list(set(plist))
def get_modules_with_version():
modules = get_modules()
@@ -310,6 +318,7 @@ def upgrade_graph(graph, cr, module_list, force=None):
mod_path = get_module_path(module)
terp_file = get_module_resource(module, '__terp__.py')
if not mod_path or not terp_file:
+ logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not installable' % (module))
cr.execute("update ir_module_module set state=%s where name=%s", ('uninstallable', module))
continue
@@ -325,6 +334,7 @@ def upgrade_graph(graph, cr, module_list, force=None):
dependencies = dict([(p, deps) for p, deps, data in packages])
current, later = set([p for p, dep, data in packages]), set()
+
while packages and current > later:
package, deps, data = packages[0]
@@ -394,7 +404,7 @@ def register_class(m):
try:
zip_mod_path = mod_path + '.zip'
if not os.path.isfile(zip_mod_path):
- fm = imp.find_module(m, [ad, _ad])
+ fm = imp.find_module(m, ad_paths)
try:
imp.load_module(m, *fm)
finally:
@@ -571,6 +581,8 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs):
has_updates = False
modobj = None
+ logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph))
+
for package in graph:
logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
migrations.migrate_module(package, 'pre')
@@ -751,7 +763,11 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
for rmod, rid in cr.fetchall():
uid = 1
- pool.get(rmod).unlink(cr, uid, [rid])
+ rmod_module= pool.get(rmod)
+ if rmod_module:
+ rmod_module.unlink(cr, uid, [rid])
+ else:
+ logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
cr.commit()
#
diff --git a/bin/addons/base/base_data.xml b/bin/addons/base/base_data.xml
index 64c5f191fbb..710bddeb73d 100644
--- a/bin/addons/base/base_data.xml
+++ b/bin/addons/base/base_data.xml
@@ -574,7 +574,7 @@
mh
- Macedonia
+ FYROM
mk
diff --git a/bin/addons/base/ir/ir.xml b/bin/addons/base/ir/ir.xml
index c6c96a91aae..1658be358d5 100644
--- a/bin/addons/base/ir/ir.xml
+++ b/bin/addons/base/ir/ir.xml
@@ -115,6 +115,8 @@
+
+
diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py
index 3524a4857a6..8bfc0740781 100644
--- a/bin/addons/base/ir/ir_actions.py
+++ b/bin/addons/base/ir/ir_actions.py
@@ -127,6 +127,7 @@ class report_xml(osv.osv):
('html', 'html'),
('raw', 'raw'),
('sxw', 'sxw'),
+ ('txt', 'txt'),
('odt', 'odt'),
('html2html','HTML from HTML'),
('mako2html','HTML from HTML(Mako)'),
diff --git a/bin/addons/base/ir/ir_attachment.py b/bin/addons/base/ir/ir_attachment.py
index 554110b3880..89d4bbd49dd 100644
--- a/bin/addons/base/ir/ir_attachment.py
+++ b/bin/addons/base/ir/ir_attachment.py
@@ -30,7 +30,7 @@ class ir_attachment(osv.osv):
ima = self.pool.get('ir.model.access')
if isinstance(ids, (int, long)):
ids = [ids]
- cr.execute('select distinct res_model from ir_attachment where id in ('+','.join(map(str, ids))+')')
+ cr.execute('select distinct res_model from ir_attachment where id = ANY (%s)', (ids,))
for obj in cr.fetchall():
if obj[0]:
ima.check(cr, uid, obj[0], mode, context=context)
diff --git a/bin/addons/base/ir/ir_cron.py b/bin/addons/base/ir/ir_cron.py
index e4cae8569c7..18889d46a2a 100644
--- a/bin/addons/base/ir/ir_cron.py
+++ b/bin/addons/base/ir/ir_cron.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
##############################################################################
#
-# OpenERP, Open Source Management Solution
+# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (). All Rights Reserved
# $Id$
#
@@ -25,7 +25,7 @@ import time
import netsvc
import tools
import pooler
-from osv import fields,osv
+from osv import fields, osv
def str2tuple(s):
return eval('tuple(%s)' % s)
@@ -76,7 +76,7 @@ class ir_cron(osv.osv, netsvc.Agent):
return False
return True
- _constraints= [
+ _constraints = [
(_check_args, 'Invalid arguments', ['args']),
]
@@ -92,7 +92,7 @@ class ir_cron(osv.osv, netsvc.Agent):
self._logger.notifyChannel('timers', netsvc.LOG_ERROR, tools.exception_to_unicode(e))
- def _poolJobs(self, db_name, check=False):
+ def _poolJobs(self, db_name, check=False):
try:
db, pool = pooler.get_db_and_pool(db_name)
except:
@@ -115,7 +115,7 @@ class ir_cron(osv.osv, netsvc.Agent):
if numbercall:
nextcall += _intervalTypes[job['interval_type']](job['interval_number'])
ok = True
- addsql=''
+ addsql = ''
if not numbercall:
addsql = ', active=False'
cr.execute("update ir_cron set nextcall=%s, numbercall=%s"+addsql+" where id=%s", (nextcall.strftime('%Y-%m-%d %H:%M:%S'), numbercall, job['id']))
@@ -124,13 +124,18 @@ class ir_cron(osv.osv, netsvc.Agent):
cr.execute('select min(nextcall) as min_next_call from ir_cron where numbercall<>0 and active and nextcall>=now()')
next_call = cr.dictfetchone()['min_next_call']
- if next_call:
+ if next_call:
next_call = time.mktime(time.strptime(next_call, '%Y-%m-%d %H:%M:%S'))
else:
next_call = int(time.time()) + 3600 # if do not find active cron job from database, it will run again after 1 day
if not check:
self.setAlarm(self._poolJobs, next_call, db_name, db_name)
+
+ except Exception, ex:
+ logger = netsvc.Logger()
+ logger.notifyChannel('cron', netsvc.LOG_WARNING,
+ 'Exception in cron:'+str(ex))
finally:
cr.commit()
diff --git a/bin/addons/base/ir/ir_sequence.py b/bin/addons/base/ir/ir_sequence.py
index f1334b6996a..2f8f75ccf5e 100644
--- a/bin/addons/base/ir/ir_sequence.py
+++ b/bin/addons/base/ir/ir_sequence.py
@@ -21,6 +21,7 @@
import time
from osv import fields,osv
+from tools.safe_eval import safe_eval
import pooler
class ir_sequence_type(osv.osv):
@@ -46,6 +47,8 @@ class ir_sequence(osv.osv):
'number_next': fields.integer('Next Number', required=True),
'number_increment': fields.integer('Increment Number', required=True),
'padding' : fields.integer('Number padding', required=True),
+ 'condition': fields.char('Condition', size=250, help="If set, sequence will only be used in case this python expression matches, and will precede other sequences."),
+ 'weight': fields.integer('Weight',required=True, help="If two sequences match, the highest weight will be used."),
'company_id' : fields.many2one('res.company', 'Company'),
}
_defaults = {
@@ -53,6 +56,7 @@ class ir_sequence(osv.osv):
'number_increment': lambda *a: 1,
'number_next': lambda *a: 1,
'padding' : lambda *a : 0,
+ 'weight' : lambda *a: 10,
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'ir.sequence', c)
}
@@ -72,10 +76,29 @@ class ir_sequence(osv.osv):
}
def get_id(self, cr, uid, sequence_id, test='id=%s', context=None):
+ if not context:
+ context = {}
try:
- cr.execute('SELECT id, number_next, prefix, suffix, padding FROM ir_sequence WHERE '+test+' AND active=%s FOR UPDATE', (sequence_id, True))
- res = cr.dictfetchone()
- if res:
+ cr.execute('SELECT id, number_next, prefix, suffix, padding, condition \
+ FROM ir_sequence \
+ WHERE '+test+' AND active=%s ORDER BY weight DESC, length(COALESCE(condition,\'\')) DESC \
+ FOR UPDATE', (sequence_id, True))
+ for res in cr.dictfetchall():
+ if res['condition']:
+ print "ir_seq: %s has condition:" %res['id'], res['condition'],
+ try:
+ bo = safe_eval(res['condition'],context)
+ if not bo:
+ print "not matched"
+ continue
+ except Exception,e:
+ # it would be normal to have exceptions, because
+ # the domain may contain errors
+ print "Exception.\ne:",e
+ print "Context:", context
+ continue
+ print "Matched!"
+
cr.execute('UPDATE ir_sequence SET number_next=number_next+number_increment WHERE id=%s AND active=%s', (res['id'], True))
if res['number_next']:
return self._process(res['prefix']) + '%%0%sd' % res['padding'] % res['number_next'] + self._process(res['suffix'])
@@ -85,8 +108,8 @@ class ir_sequence(osv.osv):
cr.commit()
return False
- def get(self, cr, uid, code):
- return self.get_id(cr, uid, code, test='code=%s')
+ def get(self, cr, uid, code, context = None):
+ return self.get_id(cr, uid, code, test='code=%s',context=context)
ir_sequence()
diff --git a/bin/addons/base/ir/ir_values.py b/bin/addons/base/ir/ir_values.py
index 64196007fd2..355a1ac01d1 100644
--- a/bin/addons/base/ir/ir_values.py
+++ b/bin/addons/base/ir/ir_values.py
@@ -249,8 +249,7 @@ class ir_values(osv.osv):
if r[2].has_key('groups_id'):
groups = r[2]['groups_id']
if len(groups) > 0:
- group_ids = ','.join([ str(x) for x in r[2]['groups_id']])
- cr.execute("select count(*) from res_groups_users_rel where gid in (%s) and uid='%s'" % (group_ids, uid))
+ cr.execute("SELECT count(*) FROM res_groups_users_rel WHERE gid = ANY(%s) AND uid=%s",(groups, uid))
gr_ids = cr.fetchall()
if not gr_ids[0][0] > 0:
res2.remove(r)
diff --git a/bin/addons/base/ir/workflow/print_instance.py b/bin/addons/base/ir/workflow/print_instance.py
index e70be5bebbe..bac8eedb476 100644
--- a/bin/addons/base/ir/workflow/print_instance.py
+++ b/bin/addons/base/ir/workflow/print_instance.py
@@ -81,7 +81,10 @@ def graph_get(cr, graph, wkf_id, nested=False, workitem={}):
start = cr.fetchone()[0]
cr.execute("select 'subflow.'||name,id from wkf_activity where flow_stop=True and wkf_id=%s", (wkf_id,))
stop = cr.fetchall()
- stop = (stop[0][1], dict(stop))
+ if (stop):
+ stop = (stop[0][1], dict(stop))
+ else:
+ stop = ("stop",{})
return ((start,{}),stop)
@@ -143,7 +146,7 @@ showpage'''
else:
inst_id = inst_id[0]
graph = pydot.Dot(fontsize='16', label="""\\\n\\nWorkflow: %s\\n OSV: %s""" % (wkfinfo['name'],wkfinfo['osv']),
- size='10.7, 7.3', center='1', ratio='auto', rotate='90', rankdir='LR'
+ size='7.3, 10.1', center='1', ratio='auto', rotate='0', rankdir='TB',
)
graph_instance_get(cr, graph, inst_id, data.get('nested', False))
ps_string = graph.create(prog='dot', format='ps')
diff --git a/bin/addons/base/module/report/ir_module_reference.rml b/bin/addons/base/module/report/ir_module_reference.rml
index 1e09766e3e6..10117c7130a 100644
--- a/bin/addons/base/module/report/ir_module_reference.rml
+++ b/bin/addons/base/module/report/ir_module_reference.rml
@@ -233,6 +233,12 @@
Object: [[ object.model ]] [[ objdoc(object.model) ]]
+
+ |
+ [[ repeatIn(objdoc2(object.model) or [], 'sline') ]]
+ [[ sline ]]
+ |
+
diff --git a/bin/addons/base/module/report/ir_module_reference_print.py b/bin/addons/base/module/report/ir_module_reference_print.py
index ff1fee02955..7364499af7e 100644
--- a/bin/addons/base/module/report/ir_module_reference_print.py
+++ b/bin/addons/base/module/report/ir_module_reference_print.py
@@ -29,11 +29,38 @@ class ir_module_reference_print(report_sxw.rml_parse):
'time': time,
'findobj': self._object_find,
'objdoc': self._object_doc,
+ 'objdoc2': self._object_doc2,
'findflds': self._fields_find,
})
def _object_doc(self, obj):
modobj = self.pool.get(obj)
- return modobj.__doc__
+ strdocs= modobj.__doc__
+ if not strdocs:
+ return None
+ else:
+ strdocs=strdocs.strip().splitlines(True)
+ res = ''
+ for stre in strdocs:
+ if not stre or stre.isspace():
+ break
+ res += stre
+ return res
+
+ def _object_doc2(self, obj):
+ modobj = self.pool.get(obj)
+ strdocs= modobj.__doc__
+ if not strdocs:
+ return None
+ else:
+ strdocs=strdocs.strip().splitlines(True)
+ res = []
+ fou = False
+ for stre in strdocs:
+ if fou:
+ res.append(stre.strip())
+ elif not stre or stre.isspace():
+ fou = True
+ return res
def _object_find(self, module):
ids2 = self.pool.get('ir.model.data').search(self.cr, self.uid, [('module','=',module), ('model','=','ir.model')])
diff --git a/bin/addons/base/module/wizard/wizard_export_lang.py b/bin/addons/base/module/wizard/wizard_export_lang.py
index 41ebbca64cb..1e538b95cfb 100644
--- a/bin/addons/base/module/wizard/wizard_export_lang.py
+++ b/bin/addons/base/module/wizard/wizard_export_lang.py
@@ -79,7 +79,8 @@ class wizard_export_lang(osv.osv_memory):
('get','get'), # get the file
) ),
}
- _defaults = { 'state': lambda *a: 'choose',
+ _defaults = { 'state': lambda *a: 'choose',
+ 'name': lambda *a: 'lang.tar.gz'
}
wizard_export_lang()
diff --git a/bin/addons/base/res/country_view.xml b/bin/addons/base/res/country_view.xml
index a1852bffd62..81459ff8860 100644
--- a/bin/addons/base/res/country_view.xml
+++ b/bin/addons/base/res/country_view.xml
@@ -71,7 +71,7 @@
- States
+ Fed. States
ir.actions.act_window
res.country.state
form
diff --git a/bin/addons/base/res/ir_property.py b/bin/addons/base/res/ir_property.py
index 952c32930cf..e14e365dce3 100644
--- a/bin/addons/base/res/ir_property.py
+++ b/bin/addons/base/res/ir_property.py
@@ -58,7 +58,7 @@ class ir_property(osv.osv):
}
def unlink(self, cr, uid, ids, context={}):
if ids:
- cr.execute('delete from ir_model_fields where id in (select fields_id from ir_property where (fields_id is not null) and (id in ('+','.join(map(str, ids))+')))')
+ cr.execute('DELETE FROM ir_model_fields WHERE id IN (SELECT fields_id FROM ir_property WHERE (fields_id IS NOT NULL) AND (id = ANY (%s)))', (ids,))
res = super(ir_property, self).unlink(cr, uid, ids, context)
return res
diff --git a/bin/addons/base/res/res_company.py b/bin/addons/base/res/res_company.py
index 3d481cdfa6d..9cfa2c4670a 100644
--- a/bin/addons/base/res/res_company.py
+++ b/bin/addons/base/res/res_company.py
@@ -113,11 +113,11 @@ class res_company(osv.osv):
-
+
[[ formatLang(time.strftime("%Y-%m-%d"), date=True) ]] [[ time.strftime("%H:%M") ]]
-
+
[[ company.partner_id.name ]]
-
+
/
@@ -136,7 +136,7 @@ class res_company(osv.osv):
[[company.logo]]
-
+
1.3cm 27.7cm 20cm 27.7cm
diff --git a/bin/addons/base/res/res_user.py b/bin/addons/base/res/res_user.py
index 5b610096012..d6b7cd6eec0 100644
--- a/bin/addons/base/res/res_user.py
+++ b/bin/addons/base/res/res_user.py
@@ -71,6 +71,14 @@ class groups(osv.osv):
aid.write({'groups_id': [(4, gid)]})
return gid
+ def copy(self, cr, uid, id, default={}, context={}, done_list=[], local=False):
+ group = self.browse(cr, uid, id, context=context)
+ default = default.copy()
+ if not 'name' in default:
+ default['name'] = group['name']
+ default['name'] = default['name'] + _(' (copy)')
+ return super(groups, self).copy(cr, uid, id, default, context=context)
+
groups()
diff --git a/bin/addons/base/rng/view.rng b/bin/addons/base/rng/view.rng
index 37f681de92f..e5db7ddf7f9 100644
--- a/bin/addons/base/rng/view.rng
+++ b/bin/addons/base/rng/view.rng
@@ -12,6 +12,7 @@
+
@@ -45,6 +46,7 @@
+
@@ -187,6 +189,7 @@
+
@@ -226,6 +229,7 @@
+
@@ -243,6 +247,7 @@
+
@@ -366,6 +371,7 @@
+
@@ -489,6 +495,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/addons/base/security/base_security.xml b/bin/addons/base/security/base_security.xml
index 19886993316..6aabe27bdba 100644
--- a/bin/addons/base/security/base_security.xml
+++ b/bin/addons/base/security/base_security.xml
@@ -25,6 +25,10 @@
Useability / No One
+
+ MultiCompany/Manager
+
+
diff --git a/bin/addons/quality_integration_server/base_quality_interrogation.py b/bin/addons/quality_integration_server/base_quality_interrogation.py
index 456b2597223..b2347e6ee0d 100755
--- a/bin/addons/quality_integration_server/base_quality_interrogation.py
+++ b/bin/addons/quality_integration_server/base_quality_interrogation.py
@@ -160,7 +160,7 @@ def check_quality(uri, user, pwd, dbname, modules, quality_logs):
def wait(id,url=''):
progress=0.0
- sock2 = xmlrpclib.ServerProxy(url+'/db')
+ sock2 = xmlrpclib.ServerProxy(url+'/xmlrpc/db')
while not progress==1.0:
progress,users = execute(sock2,'get_progress',admin_passwd, id)
return True
diff --git a/bin/import_xml.rng b/bin/import_xml.rng
index 1ba1ff3831f..1e1167656b0 100644
--- a/bin/import_xml.rng
+++ b/bin/import_xml.rng
@@ -96,6 +96,7 @@
+
@@ -118,6 +119,7 @@
+
diff --git a/bin/netsvc.py b/bin/netsvc.py
index 57d8e7831d2..8abdf4d7901 100644
--- a/bin/netsvc.py
+++ b/bin/netsvc.py
@@ -36,17 +36,23 @@ import time
import xmlrpclib
import release
-SERVICES = {}
-GROUPS = {}
-
class Service(object):
+ """ Base class for *Local* services
+
+ Functionality here is trusted, no authentication.
+ """
+ _services = {}
def __init__(self, name, audience=''):
- SERVICES[name] = self
+ Service._services[name] = self
self.__name = name
self._methods = {}
def joinGroup(self, name):
- GROUPS.setdefault(name, {})[self.__name] = self
+ raise Exception("No group for local services")
+ #GROUPS.setdefault(name, {})[self.__name] = self
+
+ def service_exist(self,name):
+ return Service._services.has_key(name)
def exportMethod(self, method):
if callable(method):
@@ -58,11 +64,16 @@ class Service(object):
else:
raise
-class LocalService(Service):
+class LocalService(object):
+ """ Proxy for local services.
+
+ Any instance of this class will behave like the single instance
+ of Service(name)
+ """
def __init__(self, name):
self.__name = name
try:
- self._service = SERVICES[name]
+ self._service = Service._services[name]
for method_name, method_definition in self._service._methods.items():
setattr(self, method_name, method_definition)
except KeyError, keyError:
@@ -71,19 +82,55 @@ class LocalService(Service):
def __call__(self, method, *params):
return getattr(self, method)(*params)
-def service_exist(name):
- return SERVICES.get(name, False)
+class ExportService(object):
+ """ Proxy for exported services.
+
+ All methods here should take an AuthProxy as their first parameter. It
+ will be appended by the calling framework.
+
+ Note that this class has no direct proxy, capable of calling
+ eservice.method(). Rather, the proxy should call
+ dispatch(method,auth,params)
+ """
+
+ _services = {}
+ _groups = {}
+
+ def __init__(self, name, audience=''):
+ ExportService._services[name] = self
+ self.__name = name
+
+ def joinGroup(self, name):
+ ExportService._groups.setdefault(name, {})[self.__name] = self
+
+ @classmethod
+ def getService(cls,name):
+ return cls._services[name]
+
+ def dispatch(self, method, auth, params):
+ raise Exception("stub dispatch at %s" % self.__name)
+
+ def new_dispatch(self,method,auth,params):
+ raise Exception("stub dispatch at %s" % self.__name)
+
+ def abortResponse(self, error, description, origin, details):
+ if not tools.config['debug_mode']:
+ raise Exception("%s -- %s\n\n%s"%(origin, description, details))
+ else:
+ raise
LOG_NOTSET = 'notset'
LOG_DEBUG_RPC = 'debug_rpc'
LOG_DEBUG = 'debug'
+LOG_DEBUG2 = 'debug2'
LOG_INFO = 'info'
LOG_WARNING = 'warn'
LOG_ERROR = 'error'
LOG_CRITICAL = 'critical'
# add new log level below DEBUG
-logging.DEBUG_RPC = logging.DEBUG - 1
+logging.DEBUG2 = logging.DEBUG - 1
+logging.DEBUG_RPC = logging.DEBUG2 - 1
def init_logger():
import os
@@ -94,7 +141,6 @@ def init_logger():
# create a format for log messages and dates
formatter = logging.Formatter('[%(asctime)s] %(levelname)s:%(name)s:%(message)s')
- logging_to_stdout = False
if tools.config['syslog']:
# SysLog Handler
if os.name == 'nt':
@@ -112,15 +158,18 @@ def init_logger():
dirname = os.path.dirname(logf)
if dirname and not os.path.isdir(dirname):
os.makedirs(dirname)
- handler = logging.handlers.TimedRotatingFileHandler(logf,'D',1,30)
+ if tools.config['logrotate'] is not False:
+ handler = logging.handlers.TimedRotatingFileHandler(logf,'D',1,30)
+ elif os.name == 'posix':
+ handler = logging.handlers.WatchedFileHandler(logf)
+ else:
+ handler = logging.handlers.FileHandler(logf)
except Exception, ex:
sys.stderr.write("ERROR: couldn't create the logfile directory. Logging to the standard output.\n")
handler = logging.StreamHandler(sys.stdout)
- logging_to_stdout = True
else:
# Normal Handler on standard output
handler = logging.StreamHandler(sys.stdout)
- logging_to_stdout = True
# tell the handler to use this format
@@ -128,9 +177,9 @@ def init_logger():
# add the handler to the root logger
logger.addHandler(handler)
- logger.setLevel(tools.config['log_level'] or '0')
+ logger.setLevel(int(tools.config['log_level'] or '0'))
- if logging_to_stdout and os.name != 'nt':
+ if (not isinstance(handler, logging.FileHandler)) and os.name != 'nt':
# change color of level names
# uses of ANSI color codes
# see http://pueblo.sourceforge.net/doc/manual/ansi_color_codes.html
@@ -141,6 +190,7 @@ def init_logger():
mapping = {
'DEBUG_RPC': ('blue', 'white'),
+ 'DEBUG2': ('green', 'white'),
'DEBUG': ('blue', 'default'),
'INFO': ('green', 'default'),
'WARNING': ('yellow', 'default'),
@@ -160,6 +210,10 @@ class Logger(object):
log = logging.getLogger(tools.ustr(name))
+ if level == LOG_DEBUG2 and not hasattr(log, level):
+ fct = lambda msg, *args, **kwargs: log.log(logging.DEBUG2, msg, *args, **kwargs)
+ setattr(log, LOG_DEBUG2, fct)
+
if level == LOG_DEBUG_RPC and not hasattr(log, level):
fct = lambda msg, *args, **kwargs: log.log(logging.DEBUG_RPC, msg, *args, **kwargs)
setattr(log, LOG_DEBUG_RPC, fct)
@@ -169,17 +223,33 @@ class Logger(object):
if isinstance(msg, Exception):
msg = tools.exception_to_unicode(msg)
- msg = tools.ustr(msg).strip()
-
- if level in (LOG_ERROR,LOG_CRITICAL):
- msg = common().get_server_environment() + '\n' + msg
+ try:
+ msg = tools.ustr(msg).strip()
+ if level in (LOG_ERROR,LOG_CRITICAL) and tools.config.get_misc('debug','env_info',True):
+ msg = common().exp_get_server_environment() + "\n" + msg
- result = msg.split('\n')
- if len(result)>1:
- for idx, s in enumerate(result):
- level_method('[%02d]: %s' % (idx+1, s,))
- elif result:
- level_method(result[0])
+ result = msg.split('\n')
+ except UnicodeDecodeError:
+ result = msg.strip().split('\n')
+ try:
+ if len(result)>1:
+ for idx, s in enumerate(result):
+ level_method('[%02d]: %s' % (idx+1, s,))
+ elif result:
+ level_method(result[0])
+ except IOError,e:
+ # TODO: perhaps reset the logger streams?
+ #if logrotate closes our files, we end up here..
+ pass
+ except:
+ # better ignore the exception and carry on..
+ pass
+
+ def set_loglevel(self, level):
+ log = logging.getLogger()
+ log.setLevel(logging.INFO) # make sure next msg is printed
+ log.info("Log level changed to %s" % logging.getLevelName(level))
+ log.setLevel(level)
def shutdown(self):
logging.shutdown()
@@ -194,7 +264,7 @@ class Agent(object):
def setAlarm(self, fn, dt, db_name, *args, **kwargs):
wait = dt - time.time()
if wait > 0:
- self._logger.notifyChannel('timers', LOG_DEBUG, "Job scheduled in %s seconds for %s.%s" % (wait, fn.im_class.__name__, fn.func_name))
+ self._logger.notifyChannel('timers', LOG_DEBUG, "Job scheduled in %.3g seconds for %s.%s" % (wait, fn.im_class.__name__, fn.func_name))
timer = threading.Timer(wait, fn, args, kwargs)
timer.start()
self._timers.setdefault(db_name, []).append(timer)
@@ -218,10 +288,66 @@ class Agent(object):
import traceback
-class xmlrpc(object):
- class RpcGateway(object):
- def __init__(self, name):
- self.name = name
+class Server:
+ """ Generic interface for all servers with an event loop etc.
+ Override this to impement http, net-rpc etc. servers.
+
+ Servers here must have threaded behaviour. start() must not block,
+ there is no run().
+ """
+ __is_started = False
+ __servers = []
+
+ def __init__(self):
+ if Server.__is_started:
+ raise Exception('All instances of servers must be inited before the startAll()')
+ Server.__servers.append(self)
+
+ def start(self):
+ print "called stub Server.start"
+ pass
+
+ def stop(self):
+ print "called stub Server.stop"
+ pass
+
+ def stats(self):
+ """ This function should return statistics about the server """
+ return "%s: No statistics" % str(self.__class__)
+
+ @classmethod
+ def startAll(cls):
+ if cls.__is_started:
+ return
+ Logger().notifyChannel("services", LOG_INFO,
+ "Starting %d services" % len(cls.__servers))
+ for srv in cls.__servers:
+ srv.start()
+ cls.__is_started = True
+
+ @classmethod
+ def quitAll(cls):
+ if not cls.__is_started:
+ return
+ Logger().notifyChannel("services", LOG_INFO,
+ "Stopping %d services" % len(cls.__servers))
+ for srv in cls.__servers:
+ srv.stop()
+ cls.__is_started = False
+
+ @classmethod
+ def allStats(cls):
+ res = ''
+ if cls.__is_started:
+ res += "Servers started\n"
+ else:
+ res += "Servers stopped\n"
+ for srv in cls.__servers:
+ try:
+ res += srv.stats() + "\n"
+ except:
+ pass
+ return res
class OpenERPDispatcherException(Exception):
def __init__(self, exception, traceback):
@@ -234,14 +360,19 @@ class OpenERPDispatcher:
Logger().notifyChannel('%s' % title, LOG_DEBUG_RPC, pformat(msg))
def dispatch(self, service_name, method, params):
- if service_name not in GROUPS['web-services']:
- raise Exception('Access Denied for Service :'+service_name)
try:
self.log('service', service_name)
self.log('method', method)
self.log('params', params)
- result = LocalService(service_name)(method, *params)
+ if hasattr(self,'auth_provider'):
+ auth = self.auth_provider
+ else:
+ auth = None
+ result = ExportService.getService(service_name).dispatch(method, auth, params)
self.log('result', result)
+ # We shouldn't marshall None,
+ if result == None:
+ result = False
return result
except Exception, e:
self.log('exception', tools.exception_to_unicode(e))
@@ -255,174 +386,4 @@ 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':
- self.server.socket.shutdown( hasattr(socket, 'SHUT_RDWR') and socket.SHUT_RDWR or 2 )
- 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/openerp-server.py b/bin/openerp-server.py
index 3d07c9de047..dbf1caa7532 100755
--- a/bin/openerp-server.py
+++ b/bin/openerp-server.py
@@ -36,6 +36,7 @@ GNU Public Licence.
import sys
import os
import signal
+import pwd
#----------------------------------------------------------
# ubuntu 8.04 has obsoleted `pyxml` package and installs here.
# the path needs to be updated before any `import xml`
@@ -48,11 +49,16 @@ if os.path.exists(_oldxml1):
elif os.path.exists(_oldxml2):
sys.path.insert(0,_oldxml2)
-
import release
__author__ = release.author
__version__ = release.version
+# We DON't log this using the standard logger, because we might mess
+# with the logfile's permissions. Just do a quick exit here.
+if pwd.getpwuid(os.getuid())[0] == 'root' :
+ sys.stderr.write("Attempted to run OpenERP server as root. This is not good, aborting.\n")
+ sys.exit(1)
+
#----------------------------------------------------------
# get logger
#----------------------------------------------------------
@@ -105,6 +111,17 @@ import addons
# Load and update databases if requested
#----------------------------------------------------------
+import service.http_server
+
+if not ( tools.config["stop_after_init"] or \
+ tools.config["translate_in"] or \
+ tools.config["translate_out"] ):
+ service.http_server.init_servers()
+ service.http_server.init_xmlrpc()
+
+ import service.netrpc_server
+ service.netrpc_server.init_servers()
+
if tools.config['db_name']:
for db in tools.config['db_name'].split(','):
pooler.get_db_and_pool(db, update_module=tools.config['init'] or tools.config['update'])
@@ -145,36 +162,9 @@ if tools.config["stop_after_init"]:
#----------------------------------------------------------
-# Launch Server
+# Launch Servers
#----------------------------------------------------------
-if tools.config['xmlrpc']:
- port = int(tools.config['port'])
- interface = tools.config["interface"]
- secure = tools.config["secure"]
-
- httpd = netsvc.HttpDaemon(interface, port, secure)
-
- xml_gw = netsvc.xmlrpc.RpcGateway('web-services')
- httpd.attach("/xmlrpc", xml_gw)
- logger.notifyChannel("web-services", netsvc.LOG_INFO,
- "starting XML-RPC%s services, port %s" %
- ((tools.config['secure'] and ' Secure' or ''), port))
-
-#
-#if tools.config["soap"]:
-# soap_gw = netsvc.xmlrpc.RpcGateway('web-services')
-# httpd.attach("/soap", soap_gw )
-# logger.notifyChannel("web-services", netsvc.LOG_INFO, 'starting SOAP services, port '+str(port))
-#
-
-if tools.config['netrpc']:
- netport = int(tools.config['netport'])
- netinterface = tools.config["netinterface"]
- tinySocket = netsvc.TinySocketServerThread(netinterface, netport, False)
- logger.notifyChannel("web-services", netsvc.LOG_INFO,
- "starting NET-RPC service, port %d" % (netport,))
-
LST_SIGNALS = ['SIGINT', 'SIGTERM']
if os.name == 'posix':
LST_SIGNALS.extend(['SIGUSR1','SIGQUIT'])
@@ -189,11 +179,8 @@ def handler(signum, _):
:param signum: the signal number
:param _:
"""
- if tools.config['netrpc']:
- tinySocket.stop()
- if tools.config['xmlrpc']:
- httpd.stop()
netsvc.Agent.quit()
+ netsvc.Server.quitAll()
if tools.config['pidfile']:
os.unlink(tools.config['pidfile'])
logger.notifyChannel('shutdown', netsvc.LOG_INFO,
@@ -210,16 +197,14 @@ if tools.config['pidfile']:
fd.write(pidtext)
fd.close()
+
+netsvc.Server.startAll()
+
logger.notifyChannel("web-services", netsvc.LOG_INFO,
'the server is running, waiting for connections...')
-if tools.config['netrpc']:
- tinySocket.start()
-if tools.config['xmlrpc']:
- httpd.start()
-
while True:
- time.sleep(1)
+ time.sleep(60)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/bin/osv/expression.py b/bin/osv/expression.py
index 0a98b7c2118..31a02e8f244 100644
--- a/bin/osv/expression.py
+++ b/bin/osv/expression.py
@@ -36,7 +36,7 @@ class expression(object):
return isinstance(element, (str, unicode)) and element in ['&', '|', '!']
def _is_leaf(self, element, internal=False):
- OPS = ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', '=ilike', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of')
+ OPS = ('=', '!=', '<>', '<=', '<', '>', '>=', '=?', '=like', '=ilike', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of')
INTERNAL_OPS = OPS + ('inselect',)
return (isinstance(element, tuple) or isinstance(element, list)) \
and len(element) == 3 \
@@ -44,6 +44,7 @@ class expression(object):
or (internal and element[1] in INTERNAL_OPS))
def __execute_recursive_in(self, cr, s, f, w, ids, op, type):
+ # todo: merge into parent query as sub-query
res = []
if ids:
if op in ['<','>','>=','<=']:
@@ -56,9 +57,8 @@ class expression(object):
subids = ids[i:i+cr.IN_MAX]
cr.execute('SELECT "%s"' \
' FROM "%s"' \
- ' WHERE "%s" in (%s)' % (s, f, w, ','.join(['%s']*len(subids))),
- subids)
- res.extend([r[0] for r in cr.fetchall()])
+ ' WHERE "%s" in (%s)' % (s, f, w, ','.join(['%s']*len(subids))),
+ subids)
else:
cr.execute('SELECT distinct("%s")' \
' FROM "%s" where "%s" is not null' % (s, f, s)),
@@ -236,6 +236,8 @@ class expression(object):
if operator == 'child_of':
if isinstance(right, basestring):
ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], 'like', limit=None)]
+ elif isinstance(right, (int, long)):
+ ids2 = list([right])
else:
ids2 = list(right)
@@ -343,6 +345,18 @@ class expression(object):
query = '(%s.%s IS NOT NULL and %s.%s != false)' % (table._table, left,table._table, left)
elif (((right == False) and (type(right)==bool)) or right is None) and (operator in ['<>', '!=']):
query = '%s.%s IS NOT NULL' % (table._table, left)
+ elif (operator == '=?'):
+ op = '='
+ if (right is False or right is None):
+ return ( 'TRUE',[])
+ if left in table._columns:
+ format = table._columns[left]._symbol_set[0]
+ query = '(%s.%s %s %s)' % (table._table, left, op, format)
+ params = table._columns[left]._symbol_set[1](right)
+ else:
+ query = "(%s.%s %s '%%s')" % (table._table, left, op)
+ params = right
+
else:
if left == 'id':
query = '%s.id %s %%s' % (table._table, operator)
diff --git a/bin/osv/fields.py b/bin/osv/fields.py
index 074dcad5c5e..dde80f0ffb9 100644
--- a/bin/osv/fields.py
+++ b/bin/osv/fields.py
@@ -459,7 +459,7 @@ class one2many(_column):
elif act[0] == 6:
obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
ids2 = act[2] or [0]
- cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id not in ('+','.join(map(str, ids2))+')', (id,))
+ cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
ids3 = map(lambda x:x[0], cr.fetchall())
obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
return result
@@ -504,7 +504,6 @@ class many2many(_column):
return res
for id in ids:
res[id] = []
- ids_s = ','.join(map(str, ids))
limit_str = self._limit is not None and ' limit %d' % self._limit or ''
obj = obj.pool.get(self._obj)
@@ -514,10 +513,10 @@ class many2many(_column):
cr.execute('SELECT '+self._rel+'.'+self._id2+','+self._rel+'.'+self._id1+' \
FROM '+self._rel+' , '+obj._table+' \
- WHERE '+self._rel+'.'+self._id1+' in ('+ids_s+') \
+ WHERE '+self._rel+'.'+self._id1+' = ANY (%s) \
AND '+self._rel+'.'+self._id2+' = '+obj._table+'.id '+d1
+limit_str+' order by '+obj._table+'.'+obj._order+' offset %s',
- d2+[offset])
+ [ids,]+d2+[offset])
for r in cr.fetchall():
res[r[1]].append(r[0])
return res
@@ -585,6 +584,16 @@ class many2many(_column):
obj.datas[id][name] = act[2]
+def get_nice_size(a):
+ (x,y) = a
+ if isinstance(y, (int,long)):
+ size = y
+ elif y:
+ y = len(y)
+ else:
+ y = 0
+ return (x, tools.human_size(size))
+
# ---------------------------------------------------------
# Function fields
# ---------------------------------------------------------
@@ -665,7 +674,7 @@ class function(_column):
if self._type == 'binary' and context.get('bin_size', False):
# convert the data returned by the function with the size of that data...
- res = dict(map(lambda (x, y): (x, tools.human_size(len(y or ''))), res.items()))
+ res = dict(map( get_nice_size, res.items()))
return res
get_memory = get
diff --git a/bin/osv/orm.py b/bin/osv/orm.py
index 04d493ce95e..c7ab469bcd4 100644
--- a/bin/osv/orm.py
+++ b/bin/osv/orm.py
@@ -94,10 +94,10 @@ class browse_null(object):
self.id = False
def __getitem__(self, name):
- return False
+ return None
def __getattr__(self, name):
- return False # XXX: return self ?
+ return None # XXX: return self ?
def __int__(self):
return False
@@ -165,7 +165,7 @@ class browse_record(object):
col = self._table._columns[name]
elif name in self._table._inherit_fields:
col = self._table._inherit_fields[name][2]
- elif hasattr(self._table, name):
+ elif hasattr(self._table, str(name)):
if isinstance(getattr(self._table, name), (types.MethodType, types.LambdaType, types.FunctionType)):
return lambda *args, **argv: getattr(self._table, name)(self._cr, self._uid, [self._id], *args, **argv)
else:
@@ -173,7 +173,7 @@ class browse_record(object):
else:
logger = netsvc.Logger()
logger.notifyChannel('orm', netsvc.LOG_ERROR, "Programming error: field '%s' does not exist in object '%s' !" % (name, self._table._name))
- return False
+ return None
# if the field is a classic one or a many2one, we'll fetch all classic and many2one fields
if col._prefetch:
@@ -205,6 +205,9 @@ class browse_record(object):
d[n].set_value(self._cr, self._uid, d[n], self, f, lang_obj)
+ if not datas:
+ # Where did those ids come from? Perhaps old entries in ir_model_data?
+ raise except_orm('NoDataError', 'Field %s in %s%s'%(name,self._table_name,str(ids)))
# create browse records for 'remote' objects
for data in datas:
for n, f in ffields:
@@ -225,6 +228,12 @@ class browse_record(object):
elif f._type in ('one2many', 'many2many') and len(data[n]):
data[n] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool.get(f._obj), self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in data[n]], self._context)
self._data[data['id']].update(data)
+ if not name in self._data[self._id]:
+ #how did this happen?
+ logger = netsvc.Logger()
+ logger.notifyChannel("browse_record", netsvc.LOG_ERROR,"Ffields: %s, datas: %s"%(str(fffields),str(datas)))
+ logger.notifyChannel("browse_record", netsvc.LOG_ERROR,"Data: %s, Table: %s"%(str(self._data[self._id]),str(self._table)))
+ raise AttributeError(_('Unknown attribute %s in %s ') % (str(name),self._table_name))
return self._data[self._id][name]
def __getattr__(self, name):
@@ -662,8 +671,10 @@ class orm_template(object):
else:
module, xml_id = current_module, line[i]
id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
- res_id = ir_model_data_obj.read(cr, uid, [id],
- ['res_id'])[0]['res_id']
+ res_res_id = ir_model_data_obj.read(cr, uid, [id],
+ ['res_id'])
+ if res_res_id:
+ res_id = res_res_id[0]['res_id']
row[field[-1][:-3]] = res_id or False
continue
if (len(field) == len(prefix)+1) and \
@@ -951,6 +962,7 @@ class orm_template(object):
and getattr(self._columns[f], arg):
res[f][arg] = getattr(self._columns[f], arg)
+ #TODO: optimize
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
@@ -1344,8 +1356,8 @@ class orm_template(object):
# otherwise, build some kind of default view
if view_type == 'form':
res = self.fields_get(cr, user, context=context)
- xml = '''''' \
- '''