From 612cd6dbf07a5907d48863e6f26e6d329c71ca8e Mon Sep 17 00:00:00 2001 From: Fabian Semal Date: Wed, 6 Aug 2008 16:03:40 +0200 Subject: [PATCH 001/169] Bubfix for dates bzr revid: fabian@tinyerp.com-20080806140340-23pmx0ihuqbbga0a --- bin/report/report_sxw.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bin/report/report_sxw.py b/bin/report/report_sxw.py index 76a50b95737..2568afdfcd8 100644 --- a/bin/report/report_sxw.py +++ b/bin/report/report_sxw.py @@ -189,11 +189,14 @@ class _date_format(str, _format): def __str__(self): if not self.object._context: return self.name + if self.name: - datedata = time.strptime(self.name, DT_FORMAT) - return time.strftime(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y'), + try : + datedata = time.strptime(self.name, DT_FORMAT) + return time.strftime(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y'), datedata) - return '' + except : + pass _fields_process = { 'float': _float_format, From 748450b9f9895ac9b9b55873e8d54999b35f6dd6 Mon Sep 17 00:00:00 2001 From: "mga@tinyerp.com" <> Date: Thu, 7 Aug 2008 16:27:47 +0530 Subject: [PATCH 002/169] Apply patch which is iven by the Amit for the view - Dashboard bzr revid: mga@tinyerp.com-20080807105747-8f5kp16zd7bxixoz --- bin/addons/base/ir/ir_ui_view.py | 70 ++++++++++++-------------------- 1 file changed, 25 insertions(+), 45 deletions(-) diff --git a/bin/addons/base/ir/ir_ui_view.py b/bin/addons/base/ir/ir_ui_view.py index aaad63c6fe5..f015478a568 100644 --- a/bin/addons/base/ir/ir_ui_view.py +++ b/bin/addons/base/ir/ir_ui_view.py @@ -47,32 +47,14 @@ def _check_xml(self, cr, uid, ids, context={}): return False return True -class CustomView(osv.osv): +class view_custom(osv.osv): _name = 'ir.ui.view.custom' _columns = { - 'name': fields.char('View Name',size=64, required=True), - 'model': fields.char('Model', size=64, required=True), - 'priority': fields.integer('Priority', required=True), - 'type': fields.selection(( - ('tree','Tree'), - ('form','Form'), - ('graph', 'Graph'), - ('calendar', 'Calendar')), 'View Type', required=True), - 'arch': fields.text('View Architecture', required=True), - 'inherit_id': fields.many2one('ir.ui.view', 'Inherited View'), - 'field_parent': fields.char('Childs Field',size=64), - 'user_id': fields.many2one('res.users', 'User'), 'ref_id': fields.many2one('ir.ui.view', 'Orignal View'), + 'user_id': fields.many2one('res.users', 'User'), + 'arch': fields.text('View Architecture', required=True), } - _defaults = { - 'arch': lambda *a: '\n\n\t\n', - 'priority': lambda *a: 16 - } - _order = "priority" - _constraints = [ - (_check_xml, 'Invalid XML for View Architecture!', ['arch']) - ] -CustomView() +view_custom() class view(osv.osv): _name = 'ir.ui.view' @@ -100,13 +82,13 @@ class view(osv.osv): def read(self,cr, uid, ids, fields=None, context={}, load='_classic_read'): - if type(ids) != type([]): + if not isinstance(ids, (list, tuple)): ids = [ids] - + result = super(view, self).read(cr, uid, ids, fields, context, load) for rs in result: - if rs.get('model',False) == 'board.board': + if rs.get('model') == 'board.board': cr.execute("select id,arch,ref_id from ir_ui_view_custom where user_id=%d and ref_id=%d", (uid, rs['id'])) oview = cr.dictfetchall() if oview: @@ -115,28 +97,26 @@ class view(osv.osv): return result def write(self, cr, uid, ids, vals, context={}): - exist = self.pool.get('ir.ui.view').browse(cr, uid, ids[0]) - if exist.model == 'board.board': - vids = self.pool.get('ir.ui.view.custom').search(cr, uid, [('user_id','=',uid), ('ref_id','=',ids[0])]) - if not vids: - old = self.pool.get('ir.ui.view').read(cr, uid, ids)[0] - del old['id'] - old['user_id'] = uid - old['ref_id'] = ids[0] - nid = self.pool.get('ir.ui.view.custom').create(cr, uid, old) - result = self.pool.get('ir.ui.view.custom').write(cr, uid, [nid], vals) - return result + if not isinstance(ids, (list, tuple)): + ids = [ids] + + exist = self.pool.get('ir.ui.view').browse(cr, uid, ids[0]) + if exist.model == 'board.board' and 'arch' in vals: + vids = self.pool.get('ir.ui.view.custom').search(cr, uid, [('user_id','=',uid), ('ref_id','=',ids[0])]) + vals2 = {'user_id': uid, 'ref_id': ids[0], 'arch': vals.pop('arch')} + + # write fields except arch to the `ir.ui.view` + result = super(view, self).write(cr, uid, vids, vals, context) + + if not vids: + self.pool.get('ir.ui.view.custom').create(cr, uid, vals2) else: - nid = vids - result = self.pool.get('ir.ui.view.custom').write(cr, uid, nid, vals) - - result = super(view, self).write(cr, uid, vids, vals, context) - return result - - result = super(view, self).write(cr, uid, ids, vals, context) - cr.commit() - return result + self.pool.get('ir.ui.view.custom').write(cr, uid, vids, vals2) + + return result + + return super(view, self).write(cr, uid, ids, vals, context) view() class view_sc(osv.osv): From 61ac299d06909fe35a29e4dcb427148f7f97f2df Mon Sep 17 00:00:00 2001 From: Jay Vora Date: Thu, 7 Aug 2008 18:38:07 +0530 Subject: [PATCH 003/169] Bugfix:Solved the problem of expression conversion of list into tuples. bzr revid: jvo@tinyerp.com-20080807130807-jfzlq06ldouddkzx --- bin/osv/expression.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/bin/osv/expression.py b/bin/osv/expression.py index 176a244ff0d..435e25f6afa 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -52,14 +52,14 @@ class expression(object): def _is_leaf(self, element): return isinstance(element, tuple) \ and len(element) == 3 \ - and element[1] in ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of') + and element[1] in ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of') def _is_expression(self, element): return isinstance(element, tuple) \ and len(element) > 2 \ and self._is_operator(element[0]) - + def __execute_recursive_in(self, cr, s, f, w, ids): res = [] for i in range(0, len(ids), cr.IN_MAX): @@ -81,8 +81,11 @@ class expression(object): exp = exp[0] else: if not self._is_operator(exp[0][0]): - exp.insert(0, '&') - exp = tuple(exp) + if isinstance(exp[0],list): + exp=tuple(exp[0]) + else: + exp.insert(0, '&') + exp = tuple(exp) else: exp = exp[0] @@ -109,7 +112,7 @@ class expression(object): return [] ids2 = table.search(cr, uid, [(parent, 'in', ids)], context=context) return ids + _rec_get(ids2, table, parent) - + if not self.__exp: return self @@ -134,7 +137,7 @@ class expression(object): self.__right = table.pool.get(field._obj).search(cr, uid, [(fargs[1], self.__operator, self.__right)], context=context) self.__operator = 'in' return self - + field_obj = table.pool.get(field._obj) if field._properties: # this is a function field @@ -150,7 +153,7 @@ class expression(object): self.__table = None self.__tables, self.__joins = [], [] self.__children = [] - + if field._fnct_search: subexp = field.search(cr, uid, table, self.__left, [self.__exp]) self.__children.append(expression(subexp).parse(cr, uid, table, context)) @@ -178,12 +181,12 @@ class expression(object): ids2 = [x[0] for x in field_obj.name_search(cr, uid, self.__right, [], 'like')] else: ids2 = self.__right - + def _rec_convert(ids): if field_obj == table: return ids return self.__execute_recursive_in(cr, field._id1, field._rel, field._id2, ids) - + self.__left, self.__operator, self.__right = 'id', 'in', _rec_convert(ids2 + _rec_get(ids2, field_obj, self.__table._parent_name)) else: if isinstance(self.__right, basestring): @@ -197,7 +200,7 @@ class expression(object): ids2 = [x[0] for x in field_obj.search_name(cr, uid, self.__right, [], 'like')] else: ids2 = list(self.__right) - + self.__operator = 'in' if field._obj <> self.__table._name: self.__right = ids2 + _rec_get(ids2, field_obj, self.__table._parent_name) @@ -209,7 +212,7 @@ class expression(object): res_ids = field_obj.name_search(cr, uid, self.__right, [], self.__operator) self.__operator = 'in' self.__right = map(lambda x: x[0], res_ids) - else: + else: # other field type if field.translate: if self.__operator in ('like', 'ilike', 'not like', 'not ilike'): @@ -263,7 +266,7 @@ class expression(object): len_after = len(params) check_nulls = len_after <> len_before query = '(1=0)' - + if len_after: if self.__left == 'id': instr = ','.join(['%d'] * len_after) @@ -293,7 +296,7 @@ class expression(object): query = '(%s.%s %s %s)' % (self.__table._table, self.__left, op, format) else: query = "(%s.%s %s '%s')" % (self.__table._table, self.__left, op, self.__right) - + add_null = False if like: if isinstance(self.__right, str): @@ -325,11 +328,11 @@ class expression(object): def __get_tables(self): return self.__tables + [child.__get_tables() for child in self.__children] - + def get_tables(self): return [ '"%s"' % t for t in set(flatten(self.__get_tables()))] - #def + #def if __name__ == '__main__': pass From 4ec0643d463fa82c9dabc4bbbde950dafbcc4919 Mon Sep 17 00:00:00 2001 From: Dhara Shah Date: Fri, 8 Aug 2008 11:55:54 +0530 Subject: [PATCH 004/169] change in report_sxw.py bzr revid: dsh@tinyerp.com-20080808062554-5rtdsricy6lezsp0 --- bin/report/report_sxw.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/report/report_sxw.py b/bin/report/report_sxw.py index 2568afdfcd8..9f427f7b812 100644 --- a/bin/report/report_sxw.py +++ b/bin/report/report_sxw.py @@ -196,7 +196,8 @@ class _date_format(str, _format): return time.strftime(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y'), datedata) except : - pass + pass + return '' _fields_process = { 'float': _float_format, From 8401c4021f915f6905621380a7976624e15dd301 Mon Sep 17 00:00:00 2001 From: "hda@tinyerp.com" <> Date: Mon, 11 Aug 2008 14:57:55 +0530 Subject: [PATCH 005/169] set report name as pdf name in reports (ref: bhoomika- sbh) bzr revid: hda@tinyerp.com-20080811092755-7qknwqfcgrlg8zuk --- bin/report/interface.py | 25 +++++++++++++++---------- bin/report/printscreen/ps_form.py | 13 ++++++++----- bin/report/printscreen/ps_list.py | 8 ++++++-- bin/report/render/rml.py | 7 ++++--- bin/report/render/rml2pdf/trml2pdf.py | 15 ++++++++++----- bin/report/report_sxw.py | 11 ++++++----- 6 files changed, 49 insertions(+), 30 deletions(-) diff --git a/bin/report/interface.py b/bin/report/interface.py index 900d6a5e07e..fafc52367b6 100644 --- a/bin/report/interface.py +++ b/bin/report/interface.py @@ -97,11 +97,16 @@ class report_rml(report_int): return xml rml = self.create_rml(cr, xml, uid, context) # file('/tmp/terp.rml','wb+').write(rml) + pool = pooler.get_pool(cr.dbname) + ir_actions_report_xml_obj = pool.get('ir.actions.report.xml') + report_xml_ids = ir_actions_report_xml_obj.search(cr, uid, + [('report_name', '=', self.name[7:])], context=context) + ftitle = ir_actions_report_xml_obj.browse(cr,uid,report_xml_ids)[0].name report_type = datas.get('report_type', 'pdf') create_doc = self.generators[report_type] - pdf = create_doc(rml) + pdf = create_doc(rml,title=ftitle) return (pdf, report_type) - + def create_xml(self, cr, uid, ids, datas, context=None): if not context: context={} @@ -115,7 +120,7 @@ class report_rml(report_int): def post_process_xml_data(self, cr, uid, xml, context=None): if not context: context={} - # find the position of the 3rd tag + # find the position of the 3rd tag # (skip the and the "root" tag) iter = re.finditer('<[^>]*>', xml) i = iter.next() @@ -185,31 +190,31 @@ class report_rml(report_int): result = style.applyStylesheet(doc, None) # save result to string xml = style.saveResultToString(result) - + style.freeStylesheet() doc.freeDoc() result.freeDoc() return xml - - def create_pdf(self, xml, logo=None): + + def create_pdf(self, xml, logo=None,title=None): if logo: self.bin_datas['logo'] = logo else: if 'logo' in self.bin_datas: del self.bin_datas['logo'] - obj = render.rml(xml, self.bin_datas, tools.config['root_path']) + obj = render.rml(xml, self.bin_datas, tools.config['root_path'],title) obj.render() return obj.get() - def create_html(self, xml, logo=None): + def create_html(self, xml, logo=None,title=None): obj = render.rml2html(xml, self.bin_datas) obj.render() return obj.get() - def create_raw(self, xml, logo=None): + def create_raw(self, xml, logo=None,title=None): return xml - def create_sxw(self, path, logo=None): + def create_sxw(self, path, logo=None,title=None): return path from report_sxw import report_sxw diff --git a/bin/report/printscreen/ps_form.py b/bin/report/printscreen/ps_form.py index ceba82c0dde..6e8b2de602f 100644 --- a/bin/report/printscreen/ps_form.py +++ b/bin/report/printscreen/ps_form.py @@ -39,7 +39,7 @@ import libxml2 import libxslt import time, os - +ftitle="" class report_printscreen_list(report_int): def __init__(self, name): report_int.__init__(self, name) @@ -63,6 +63,7 @@ class report_printscreen_list(report_int): return self._parse_node(dom) def create(self, cr, uid, ids, datas, context=None): + global ftitle if not context: context={} datas['ids'] = ids @@ -70,8 +71,10 @@ class report_printscreen_list(report_int): model_id = pool.get('ir.model').search(cr, uid, [('model','=',model._name)]) if model_id: model_desc = pool.get('ir.model').browse(cr, uid, model_id, context).name + ftitle=model_desc else: model_desc = model._description + ftitle=model_desc model = pool.get(datas['model']) result = model.fields_view_get(cr, uid, view_type='tree', context=context) @@ -128,7 +131,7 @@ class report_printscreen_list(report_int): field_txt = new_doc.createTextNode(str(fields[f]['string'])) field.appendChild(field_txt) header.appendChild(field) - + new_doc.childNodes[0].appendChild(header) lines = new_doc.createElement("lines") @@ -154,9 +157,9 @@ class report_printscreen_list(report_int): style = libxslt.parseStylesheetDoc(styledoc) doc = libxml2.parseDoc(new_doc.toxml()) rml_obj = style.applyStylesheet(doc, None) - rml = style.saveResultToString(rml_obj) - - self.obj = render.rml(rml) + rml = style.saveResultToString(rml_obj) + global ftitle + self.obj = render.rml(rml,ftitle) self.obj.render() return True report_printscreen_list('report.printscreen.form') diff --git a/bin/report/printscreen/ps_list.py b/bin/report/printscreen/ps_list.py index f6323a410b6..4717fe03437 100644 --- a/bin/report/printscreen/ps_list.py +++ b/bin/report/printscreen/ps_list.py @@ -39,7 +39,7 @@ import libxml2 import libxslt import time, os - +ftitle="" class report_printscreen_list(report_int): def __init__(self, name): report_int.__init__(self, name) @@ -63,6 +63,7 @@ class report_printscreen_list(report_int): return self._parse_node(dom) def create(self, cr, uid, ids, datas, context=None): + global ftitle if not context: context={} pool = pooler.get_pool(cr.dbname) @@ -70,8 +71,10 @@ class report_printscreen_list(report_int): model_id = pool.get('ir.model').search(cr, uid, [('model','=',model._name)]) if model_id: model_desc = pool.get('ir.model').browse(cr, uid, model_id[0], context).name + ftitle=model_desc else: model_desc = model._description + ftitle=model_desc datas['ids'] = ids model = pooler.get_pool(cr.dbname).get(datas['model']) @@ -199,7 +202,8 @@ class report_printscreen_list(report_int): doc = libxml2.parseDoc(new_doc.toxml()) rml_obj = style.applyStylesheet(doc, None) rml = style.saveResultToString(rml_obj) - self.obj = render.rml(rml) + global ftitle + self.obj = render.rml(rml,title=ftitle) self.obj.render() return True report_printscreen_list('report.printscreen.list') diff --git a/bin/report/render/rml.py b/bin/report/render/rml.py index f8b59bdb686..f8fd0d70614 100644 --- a/bin/report/render/rml.py +++ b/bin/report/render/rml.py @@ -33,21 +33,22 @@ import rml2pdf import rml2html as htmlizer class rml(render.render): - def __init__(self, xml, datas={}, path='.'): + def __init__(self, xml, datas={}, path='.',title=None): render.render.__init__(self, datas) self.xml = xml self.output_type = 'pdf' self.path = path + self.title=title def _render(self): - return rml2pdf.parseString(self.xml, images=self.bin_datas, path=self.path) + return rml2pdf.parseString(self.xml, images=self.bin_datas, path=self.path,title=self.title) class rml2html(render.render): def __init__(self, xml, datas={}): super(rml2html, self).__init__(datas) self.xml = xml self.output_type = 'html' - + def _render(self): return htmlizer.parseString(self.xml) diff --git a/bin/report/render/rml2pdf/trml2pdf.py b/bin/report/render/rml2pdf/trml2pdf.py index e90e91b58aa..e99209482c4 100644 --- a/bin/report/render/rml2pdf/trml2pdf.py +++ b/bin/report/render/rml2pdf/trml2pdf.py @@ -60,7 +60,7 @@ from reportlab import platypus import utils import color import os - +ftitle="" # # Change this to UTF-8 if you plan tu use Reportlab's UTF-8 support # @@ -110,7 +110,7 @@ class _rml_styles(object): for attr in ['fontName', 'bulletFontName', 'bulletText']: if node.hasAttribute(attr): data[attr] = node.getAttribute(attr) - for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter', + for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter', 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading', 'borderWidth','borderPadding','borderRadius']: if node.hasAttribute(attr): @@ -248,6 +248,9 @@ class _rml_canvas(object): self.doc = doc self.images = images self.path = path + global ftitle + if ftitle: + self.canvas.setTitle(ftitle) def _textual(self, node, x=0, y=0): rc = '' @@ -701,7 +704,7 @@ class _rml_flowable(object): node = node_story.firstChild while node: if node.nodeType == node.ELEMENT_NODE: - flow = self._flowable(node) + flow = self._flowable(node) if flow: if type(flow) == type([]): story = story + flow @@ -777,11 +780,13 @@ class _rml_template(object): fis.append(PageCount()) fis.append(platypus.PageBreak()) - + self.doc_tmpl.build(fis) -def parseString(data, fout=None, images={}, path='.'): +def parseString(data, fout=None, images={}, path='.',title=None): r = _rml_doc(data, images, path) + global ftitle + ftitle=title if fout: fp = file(fout,'wb') r.render(fp) diff --git a/bin/report/report_sxw.py b/bin/report/report_sxw.py index 9f427f7b812..825c484eb9f 100644 --- a/bin/report/report_sxw.py +++ b/bin/report/report_sxw.py @@ -197,7 +197,7 @@ class _date_format(str, _format): datedata) except : pass - return '' + return '' _fields_process = { 'float': _float_format, @@ -415,7 +415,7 @@ class rml_parse(object): res = self._regex.findall(text) todo = [] # translate the text - # the "split [[]] if not match [[]]" is not very nice, but I + # the "split [[]] if not match [[]]" is not very nice, but I # don't see how I could do it better... # what I'd like to do is a re.sub(NOT pattern, func, string) # but I don't know how to do that... @@ -444,7 +444,7 @@ class rml_parse(object): #if not isinstance(newtext, basestring): newtext = str(newtext) # if there are two [[]] blocks the same, it will replace both - # but it's ok because it should evaluate to the same thing + # but it's ok because it should evaluate to the same thing # anyway text = text.replace('[['+key+']]', newtext.decode('utf8')) self._node.data = text @@ -485,7 +485,7 @@ class rml_parse(object): self.localcontext.update(self.node_context[self._node]) if self._node.nodeType in (self._node.CDATA_SECTION_NODE, self._node.TEXT_NODE): # if self._node in self.already: -# self.already[self._node] += 1 +# self.already[self._node] += 1 # print "second pass!", self.already[self._node], '---%s---' % self._node.data # else: # self.already[self._node] = 0 @@ -660,7 +660,8 @@ class report_sxw(report_rml): logo = base64.decodestring(rml_parser.logo) create_doc = self.generators[report_type] - pdf = create_doc(rml2, logo) + title = ir_actions_report_xml_obj.browse(cr,uid,report_xml_ids)[0].name + pdf = create_doc(rml2, logo,title) return (pdf, report_type) From 53c85092d19ec77e41438b26a572371c48b14ba7 Mon Sep 17 00:00:00 2001 From: "mga@tinyerp.com" <> Date: Mon, 11 Aug 2008 19:04:24 +0530 Subject: [PATCH 006/169] change partner view put the properties in the extended groups bzr revid: mga@tinyerp.com-20080811133424-gjv6gfen2mvfiv3c --- bin/addons/base/res/partner/partner_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/addons/base/res/partner/partner_view.xml b/bin/addons/base/res/partner/partner_view.xml index 8f16f953615..ba485352921 100644 --- a/bin/addons/base/res/partner/partner_view.xml +++ b/bin/addons/base/res/partner/partner_view.xml @@ -279,7 +279,7 @@ - + From adedf0caf3c2018a85a9392ab1bdb126cb707f8c Mon Sep 17 00:00:00 2001 From: "hda@tinyerp.com" <> Date: Tue, 12 Aug 2008 14:28:05 +0530 Subject: [PATCH 007/169] bugfix for report calling from wizard bzr revid: hda@tinyerp.com-20080812085805-ygtvk1flpxvrptm0 --- bin/report/report_sxw.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/report/report_sxw.py b/bin/report/report_sxw.py index 825c484eb9f..fb9e083e85b 100644 --- a/bin/report/report_sxw.py +++ b/bin/report/report_sxw.py @@ -571,7 +571,9 @@ class report_sxw(report_rml): [('report_name', '=', self.name[7:])], context=context) report_type = 'pdf' report_xml = None + title='' if report_xml_ids: + title = ir_actions_report_xml_obj.browse(cr,uid,report_xml_ids)[0].name report_xml = ir_actions_report_xml_obj.browse(cr, uid, report_xml_ids[0], context=context) rml = report_xml.report_rml_content @@ -660,7 +662,6 @@ class report_sxw(report_rml): logo = base64.decodestring(rml_parser.logo) create_doc = self.generators[report_type] - title = ir_actions_report_xml_obj.browse(cr,uid,report_xml_ids)[0].name pdf = create_doc(rml2, logo,title) return (pdf, report_type) From 4c2a727132ddc99e1794d594a46d73cfc8ead353 Mon Sep 17 00:00:00 2001 From: Olivier Laurent Date: Tue, 12 Aug 2008 14:47:25 +0200 Subject: [PATCH 008/169] Bugfix: a dot was missing: self__operator -> self.__operator bzr revid: olt@tinyerp.com-20080812124725-m8ybax8x1q2l4ly2 --- bin/osv/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/osv/expression.py b/bin/osv/expression.py index 435e25f6afa..63c6918e04c 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -143,7 +143,7 @@ class expression(object): # this is a function field if not field._fnct_search and not field.store: # the function field doesn't provide a search function and doesn't store values in the database, so we must ignore it : we generate a dummy leaf - self.__left, self__operator, self.__right = 1, '=', 1 + self.__left, self.__operator, self.__right = 1, '=', 1 self.__exp = '' # force to generate an empty sql expression else: # we need to replace this leaf to a '&' expression From cc5a062faf56e86b39e604d9432b5237e87ad018 Mon Sep 17 00:00:00 2001 From: Olivier Laurent Date: Tue, 12 Aug 2008 16:44:56 +0200 Subject: [PATCH 009/169] pep8 bzr revid: olt@tinyerp.com-20080812144456-xvh2y1ybziok9twi --- bin/netsvc.py | 180 +-- bin/osv/expression.py | 15 +- bin/osv/fields.py | 221 ++-- bin/osv/orm.py | 467 ++++---- bin/osv/osv.py | 2591 ++++++++++++++++++++++++++++++++++++----- bin/pooler.py | 9 +- bin/tinyerp-server.py | 29 +- 7 files changed, 2792 insertions(+), 720 deletions(-) diff --git a/bin/netsvc.py b/bin/netsvc.py index df47a56a853..c8e1f0d3b36 100644 --- a/bin/netsvc.py +++ b/bin/netsvc.py @@ -31,56 +31,62 @@ import time import threading -import SimpleXMLRPCServer,signal,sys,xmlrpclib +import SimpleXMLRPCServer, signal, sys, xmlrpclib import SocketServer import socket import logging import os -_service={} -_group={} -_res_id=1 -_res={} +_service = {} +_group = {} +_res_id = 1 +_res = {} + class ServiceEndPointCall(object): - def __init__(self,id,method): - self._id=id - self._meth=method - def __call__(self,*args): - _res[self._id]=self._meth(*args) + def __init__(self, id, method): + self._id = id + self._meth = method + + def __call__(self, *args): + _res[self._id] = self._meth(*args) return self._id + class ServiceEndPoint(object): def __init__(self, name, id): self._id = id - self._meth={} - s=_service[name] + self._meth = {} + s = _service[name] for m in s._method: - self._meth[m]=s._method[m] + self._meth[m] = s._method[m] + def __getattr__(self, name): return ServiceEndPointCall(self._id, self._meth[name]) + class Service(object): _serviceEndPointID = 0 - def __init__(self, name, audience=''): - _service[name]=self - self.__name=name - self._method={} - self.exportedMethods=None - self._response_process=None - self._response_process_id=None - self._response=None - def joinGroup(self,name): + def __init__(self, name, audience=''): + _service[name] = self + self.__name = name + self._method = {} + self.exportedMethods = None + self._response_process = None + self._response_process_id = None + self._response = None + + def joinGroup(self, name): if not name in _group: - _group[name]={} - _group[name][self.__name]=self + _group[name] = {} + _group[name][self.__name] = self def exportMethod(self, m): if callable(m): - self._method[m.__name__]=m + self._method[m.__name__] = m - def serviceEndPoint(self,s): + def serviceEndPoint(self, s): if Service._serviceEndPointID >= 2**16: Service._serviceEndPointID = 0 Service._serviceEndPointID += 1 @@ -89,57 +95,62 @@ class Service(object): def conversationId(self): return 1 - def processResponse(self,s,id): + def processResponse(self, s, id): self._response_process, self._response_process_id = s, id - def processFailure(self,s,id): + def processFailure(self, s, id): pass - def resumeResponse(self,s): + def resumeResponse(self, s): pass - def cancelResponse(self,s): + def cancelResponse(self, s): pass - def suspendResponse(self,s): + def suspendResponse(self, s): if self._response_process: self._response_process(self._response_process_id, _res[self._response_process_id]) - self._response_process=None - self._response=s(self._response_process_id) + self._response_process = None + self._response = s(self._response_process_id) def abortResponse(self, error, description, origin, details): import tools if not tools.config['debug_mode']: - raise Exception("%s -- %s\n\n%s"%(origin,description,details)) + raise Exception("%s -- %s\n\n%s"%(origin, description, details)) else: raise - def currentFailure(self,s): + def currentFailure(self, s): pass + class LocalService(Service): def __init__(self, name): - self.__name=name - s=_service[name] - self._service=s + self.__name = name + s = _service[name] + self._service = s for m in s._method: - setattr(self,m,s._method[m]) + setattr(self, m, s._method[m]) + class ServiceUnavailable(Exception): pass + def service_exist(name): return (name in _service) and bool(_service[name]) + def get_rpc_paths(): return map(lambda s: '/xmlrpc/%s' % s, _service) -LOG_DEBUG='debug' -LOG_INFO='info' -LOG_WARNING='warn' -LOG_ERROR='error' -LOG_CRITICAL='critical' +LOG_DEBUG = 'debug' +LOG_INFO = 'info' +LOG_WARNING = 'warn' +LOG_ERROR = 'error' +LOG_CRITICAL = 'critical' + def init_logger(): from tools import config @@ -175,9 +186,10 @@ def init_logger(): class Logger(object): - def notifyChannel(self,name,level,msg): + def notifyChannel(self, name, level, msg): log = logging.getLogger(name) - getattr(log,level)(msg) + getattr(log, level)(msg) + class Agent(object): _timers = [] @@ -185,9 +197,9 @@ class Agent(object): def setAlarm(self, fn, dt, args=None, kwargs=None): if not args: - args=[] + args = [] if not kwargs: - kwargs={} + 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)) @@ -201,42 +213,48 @@ class Agent(object): def quit(cls): for timer in cls._timers: timer.cancel() - quit=classmethod(quit) + quit = classmethod(quit) + class RpcGateway(object): def __init__(self, name): - self.name=name + self.name = name + class Dispatcher(object): def __init__(self): pass - def monitor(self,signal): + + def monitor(self, signal): pass + def run(self): pass + class xmlrpc(object): class RpcGateway(object): def __init__(self, name): - self.name=name + self.name = name + class GenericXMLRPCRequestHandler: def _dispatch(self, method, params): import traceback try: - n=self.path.split("/")[-1] - s=LocalService(n) - m=getattr(s,method) - s._service._response=None - r=m(*params) - res=s._service._response - if res!=None: - r=res + n = self.path.split("/")[-1] + s = LocalService(n) + m = getattr(s, method) + s._service._response = None + r = m(*params) + res = s._service._response + if res != None: + r = res return r except Exception, e: tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback)) - s=str(e) + s = str(e) import tools if tools.config['debug_mode']: import pdb @@ -244,10 +262,12 @@ class GenericXMLRPCRequestHandler: pdb.post_mortem(tb) raise xmlrpclib.Fault(s, tb_s) + class SimpleXMLRPCRequestHandler(GenericXMLRPCRequestHandler, SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.rpc_paths = get_rpc_paths() + class SimpleThreadedXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): @@ -256,12 +276,13 @@ class SimpleThreadedXMLRPCServer(SocketServer.ThreadingMixIn, socket.SO_REUSEADDR, 1) SimpleXMLRPCServer.SimpleXMLRPCServer.server_bind(self) + class HttpDaemon(threading.Thread): - def __init__(self, interface,port, secure=False): + def __init__(self, interface, port, secure=False): threading.Thread.__init__(self) - self.__port=port - self.__interface=interface + self.__port = port + self.__interface = interface self.secure = secure if secure: from ssl import SecureXMLRPCServer @@ -277,17 +298,17 @@ class HttpDaemon(threading.Thread): SecureXMLRPCServer.SecureXMLRPCServer.server_bind(self) self.server = SecureThreadedXMLRPCServer((interface, port), - SecureXMLRPCRequestHandler,0) + SecureXMLRPCRequestHandler, 0) else: self.server = SimpleThreadedXMLRPCServer((interface, port), - SimpleXMLRPCRequestHandler,0) + SimpleXMLRPCRequestHandler, 0) - def attach(self,path,gw): + def attach(self, path, gw): pass def stop(self): self.running = False - if os.name <> 'nt': + if os.name != 'nt': if hasattr(socket, 'SHUT_RDWR'): if self.secure: self.server.socket.sock_shutdown(socket.SHUT_RDWR) @@ -342,17 +363,17 @@ class TinySocketClientThread(threading.Thread): self.threads.remove(self) return False try: - s=LocalService(msg[0]) - m=getattr(s,msg[1]) - s._service._response=None - r=m(*msg[2:]) - res=s._service._response - if res!=None: - r=res + s = LocalService(msg[0]) + m = getattr(s, msg[1]) + s._service._response = None + r = m(*msg[2:]) + res = s._service._response + if res != None: + r = res ts.mysend(r) except Exception, e: tb_s = reduce(lambda x, y: x+y, traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) - s=str(e) + s = str(e) import tools if tools.config['debug_mode']: import pdb @@ -368,11 +389,12 @@ class TinySocketClientThread(threading.Thread): 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.__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)) @@ -394,7 +416,7 @@ class TinySocketServerThread(threading.Thread): return False def stop(self): - self.running=False + self.running = False for t in self.threads: t.stop() try: diff --git a/bin/osv/expression.py b/bin/osv/expression.py index 63c6918e04c..050322a5e14 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -3,6 +3,7 @@ from tools import flatten + class expression(object): """ parse a domain expression @@ -47,7 +48,7 @@ class expression(object): def _is_operator(self, element): return isinstance(element, str) \ - and element in ['&','|'] + and element in ['&', '|'] def _is_leaf(self, element): return isinstance(element, tuple) \ @@ -103,7 +104,7 @@ class expression(object): if isinstance(self.__right, list): self.__right = tuple(self.__right) elif exp and not self._is_expression(self.__exp): - raise ValueError, 'Bad expression: %r' % (self.__exp,) + raise ValueError('Bad expression: %r' % (self.__exp,)) def parse(self, cr, uid, table, context): @@ -162,7 +163,7 @@ class expression(object): self.__left, self.__right = None, None self.__operator = '&' - self.__exp = ('&',) + tuple( [tuple(e.__exp) for e in self.__children] ) + self.__exp = ('&',) + tuple([tuple(e.__exp) for e in self.__children]) elif field._type == 'one2many': if isinstance(self.__right, basestring): @@ -202,7 +203,7 @@ class expression(object): ids2 = list(self.__right) self.__operator = 'in' - if field._obj <> self.__table._name: + if field._obj != self.__table._name: self.__right = ids2 + _rec_get(ids2, field_obj, self.__table._parent_name) else: self.__right = ids2 + _rec_get(ids2, self.__table, self.__left) @@ -264,7 +265,7 @@ class expression(object): del params[i] len_after = len(params) - check_nulls = len_after <> len_before + check_nulls = len_after != len_before query = '(1=0)' if len_after: @@ -323,14 +324,14 @@ class expression(object): else: children = [child.to_sql() for child in self.__children] params = flatten([child[1] for child in children]) - query = "( %s )" % (" %s " % {'&' : 'AND', '|' : 'OR' }[self.__operator]).join([child[0] for child in children if child[0]]) + query = "( %s )" % (" %s " % {'&': 'AND', '|': 'OR'}[self.__operator]).join([child[0] for child in children if child[0]]) return (query, params) def __get_tables(self): return self.__tables + [child.__get_tables() for child in self.__children] def get_tables(self): - return [ '"%s"' % t for t in set(flatten(self.__get_tables()))] + return ['"%s"' % t for t in set(flatten(self.__get_tables()))] #def diff --git a/bin/osv/fields.py b/bin/osv/fields.py index 6983fa2292b..f0de6e97bf1 100644 --- a/bin/osv/fields.py +++ b/bin/osv/fields.py @@ -49,13 +49,15 @@ import warnings import tools + def _symbol_set(symb): - if symb==None or symb==False: + if symb == None or symb == False: return None elif isinstance(symb, unicode): return symb.encode('utf-8') return str(symb) + class _column(object): _classic_read = True _classic_write = True @@ -79,13 +81,13 @@ class _column(object): self.ondelete = ondelete self.translate = translate self._domain = domain or [] - self.relate =False + self.relate = False self._context = context self.group_name = False self.write = False self.read = False self.view_load = 0 - self.select=select + self.select = select for a in args: if args[a]: setattr(self, a, args[a]) @@ -96,21 +98,25 @@ class _column(object): pass def set(self, cr, obj, id, name, value, user=None, context=None): - cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%d', (self._symbol_set[1](value),id) ) + cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%d', (self._symbol_set[1](value), id)) + def set_memory(self, cr, obj, id, name, value, user=None, context=None): - raise Exception, _('Not implemented set_memory method !') + raise Exception(_('Not implemented set_memory method !')) def get_memory(self, cr, obj, ids, name, context=None, values=None): - raise Exception, _('Not implemented get_memory method !') + raise Exception(_('Not implemented get_memory method !')) + def get(self, cr, obj, ids, name, context=None, values=None): - raise Exception, _('undefined get method !') + 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) + ids = obj.search(cr, uid, args+self._domain+[(name, 'ilike', value)], offset, limit) res = obj.read(cr, uid, ids, [name]) return [x[name] for x in res] + def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, context=None): - raise Exception, _('Not implemented search_memory method !') + raise Exception(_('Not implemented search_memory method !')) + # --------------------------------------------------------- # Simple fields @@ -121,34 +127,38 @@ class boolean(_column): _symbol_f = lambda x: x and 'True' or 'False' _symbol_set = (_symbol_c, _symbol_f) + class integer(_column): _type = 'integer' _symbol_c = '%d' _symbol_f = lambda x: int(x or 0) _symbol_set = (_symbol_c, _symbol_f) + class reference(_column): _type = 'reference' + def __init__(self, string, selection, size, **args): _column.__init__(self, string=string, size=size, selection=selection, **args) + class char(_column): _type = 'char' - + def __init__(self, string, size, **args): _column.__init__(self, string=string, size=size, **args) self._symbol_set = (self._symbol_c, self._symbol_set_char) - + # takes a string (encoded in utf8) and returns a string (encoded in utf8) def _symbol_set_char(self, symb): - #TODO: - # * we need to remove the "symb==False" from the next line BUT + #TODO: + # * we need to remove the "symb==False" from the next line BUT # for now too many things rely on this broken behavior # * the symb==None test should be common to all data types - if symb==None or symb==False: + if symb == None or symb == False: return None - # we need to convert the string to a unicode object to be able + # we need to convert the string to a unicode object to be able # to evaluate its length (and possibly truncate it) reliably if isinstance(symb, str): u_symb = unicode(symb, 'utf8') @@ -158,38 +168,46 @@ class char(_column): u_symb = unicode(symb) return u_symb.encode('utf8')[:self.size] + class text(_column): _type = 'text' import __builtin__ + class float(_column): _type = 'float' _symbol_c = '%f' _symbol_f = lambda x: __builtin__.float(x or 0.0) _symbol_set = (_symbol_c, _symbol_f) + def __init__(self, string='unknown', digits=None, **args): _column.__init__(self, string=string, **args) self.digits = digits + class date(_column): _type = 'date' + class datetime(_column): _type = 'datetime' + class time(_column): _type = 'time' + class binary(_column): _type = 'binary' _symbol_c = '%s' _symbol_f = lambda symb: symb and psycopg.Binary(symb) or None _symbol_set = (_symbol_c, _symbol_f) + class selection(_column): _type = 'selection' - + def __init__(self, selection, string='unknown', **args): _column.__init__(self, string=string, **args) self.selection = selection @@ -211,7 +229,7 @@ class one2one(_column): _classic_read = False _classic_write = True _type = 'one2one' - + def __init__(self, obj, string='unknown', **args): warnings.warn("The one2one field doesn't work anymore", DeprecationWarning) _column.__init__(self, string=string, **args) @@ -219,25 +237,26 @@ class one2one(_column): def set(self, cr, obj_src, id, field, act, user=None, context=None): if not context: - context={} + context = {} obj = obj_src.pool.get(self._obj) self._table = obj_src.pool.get(self._obj)._table - if act[0]==0: + if act[0] == 0: id_new = obj.create(cr, user, act[1]) - cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (id_new,id)) + cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (id_new, id)) else: cr.execute('select '+field+' from '+obj_src._table+' where id=%d', (act[0],)) - id =cr.fetchone()[0] - obj.write(cr, user, [id] , act[1], context=context) + 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) + return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit) + class many2one(_column): _classic_read = False _classic_write = True _type = 'many2one' - + def __init__(self, obj, string='unknown', **args): _column.__init__(self, string=string, **args) self._obj = obj @@ -254,9 +273,9 @@ class many2one(_column): def get(self, cr, obj, ids, name, user=None, context=None, values=None): if not context: - context={} + context = {} if not values: - values={} + values = {} res = {} for r in values: res[r['id']] = r[name] @@ -268,10 +287,10 @@ class many2one(_column): try: names = dict(obj.name_get(cr, user, filter(None, res.values()), context)) except except_orm: - names={} + names = {} iids = filter(None, res.values()) - cr.execute('select id,'+obj._rec_name+' from '+obj._table+' where id in ('+','.join(map(str,iids))+')') + cr.execute('select id,'+obj._rec_name+' from '+obj._table+' where id in ('+','.join(map(str, iids))+')') for res22 in cr.fetchall(): names[res22[0]] = res22[1] @@ -284,36 +303,37 @@ class many2one(_column): def set(self, cr, obj_src, id, field, values, user=None, context=None): if not context: - context={} + context = {} obj = obj_src.pool.get(self._obj) self._table = obj_src.pool.get(self._obj)._table if type(values)==type([]): for act in values: - if act[0]==0: + if act[0] == 0: id_new = obj.create(cr, act[2]) - cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (id_new,id)) - elif act[0]==1: + cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (id_new, id)) + elif act[0] == 1: obj.write(cr, [act[1]], act[2], context=context) - elif act[0]==2: + elif act[0] == 2: cr.execute('delete from '+self._table+' where id=%d', (act[1],)) - elif act[0]==3 or act[0]==5: + elif act[0] == 3 or act[0] == 5: cr.execute('update '+obj_src._table+' set '+field+'=null where id=%d', (id,)) - elif act[0]==4: - cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (act[1],id)) + elif act[0] == 4: + cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (act[1], id)) else: if values: - cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (values,id)) + cr.execute('update '+obj_src._table+' set '+field+'=%d where id=%d', (values, id)) else: cr.execute('update '+obj_src._table+' set '+field+'=null where id=%d', (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) + return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', 'like', value)], offset, limit) + class one2many(_column): _classic_read = False _classic_write = False _type = 'one2many' - + def __init__(self, obj, fields_id, string='unknown', limit=None, **args): _column.__init__(self, string=string, **args) self._obj = obj @@ -330,42 +350,41 @@ class one2many(_column): res = {} for id in ids: res[id] = [] - ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id,'in',ids)], limit=self._limit) + ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id, 'in', ids)], limit=self._limit) for r in obj.pool.get(self._obj).read(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'): if r[self._fields_id] in res: - res[r[self._fields_id]].append( r['id'] ) + res[r[self._fields_id]].append(r['id']) return res def set_memory(self, cr, obj, id, field, values, user=None, context=None): if not context: - context={} + context = {} if not values: return obj = obj.pool.get(self._obj) for act in values: - if act[0]==0: + if act[0] == 0: act[2][self._fields_id] = id obj.create(cr, user, act[2], context=context) - elif act[0]==1: - obj.write(cr, user, [act[1]] , act[2], context=context) - elif act[0]==2: + elif act[0] == 1: + obj.write(cr, user, [act[1]], act[2], context=context) + elif act[0] == 2: obj.unlink(cr, user, [act[1]], context=context) - elif act[0]==3: + elif act[0] == 3: obj.datas[act[1]][self._fields_id] = False - elif act[0]==4: + elif act[0] == 4: obj.datas[act[1]] = id - elif act[0]==5: + elif act[0] == 5: for o in obj.datas.values(): - if o[self._fields_id]==id: + if o[self._fields_id] == id: o[self._fields_id] = False - elif act[0]==6: + elif act[0] == 6: for id2 in (act[2] or []): obj.datas[id2][self._fields_id] = id def search_memory(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None): raise _('Not Implemented') - def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None): if not context: context = {} @@ -374,33 +393,33 @@ class one2many(_column): res = {} for id in ids: res[id] = [] - ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id,'in',ids)], limit=self._limit) + ids2 = obj.pool.get(self._obj).search(cr, user, [(self._fields_id, 'in', ids)], limit=self._limit) for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'): - res[r[self._fields_id]].append( r['id'] ) + res[r[self._fields_id]].append(r['id']) return res def set(self, cr, obj, id, field, values, user=None, context=None): if not context: - context={} + context = {} if not values: return _table = obj.pool.get(self._obj)._table obj = obj.pool.get(self._obj) for act in values: - if act[0]==0: + if act[0] == 0: act[2][self._fields_id] = id obj.create(cr, user, act[2], context=context) - elif act[0]==1: - obj.write(cr, user, [act[1]] , act[2], context=context) - elif act[0]==2: + elif act[0] == 1: + obj.write(cr, user, [act[1]], act[2], context=context) + elif act[0] == 2: obj.unlink(cr, user, [act[1]], context=context) - elif act[0]==3: + elif act[0] == 3: cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%d', (act[1],)) - elif act[0]==4: - cr.execute('update '+_table+' set '+self._fields_id+'=%d where id=%d', (id,act[1])) - elif act[0]==5: + elif act[0] == 4: + cr.execute('update '+_table+' set '+self._fields_id+'=%d where id=%d', (id, act[1])) + elif act[0] == 5: cr.execute('update '+_table+' set '+self._fields_id+'=null where '+self._fields_id+'=%d', (id,)) - elif act[0]==6: + elif act[0] == 6: if not act[2]: ids2 = [0] else: @@ -408,9 +427,11 @@ class one2many(_column): cr.execute('update '+_table+' set '+self._fields_id+'=NULL where '+self._fields_id+'=%d and id not in ('+','.join(map(str, ids2))+')', (id,)) if act[2]: cr.execute('update '+_table+' set '+self._fields_id+'=%d where id in ('+','.join(map(str, act[2]))+')', (id,)) + 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) + # # Values: (0, 0, { fields }) create # (1, ID, { fields }) modification @@ -424,7 +445,7 @@ class many2many(_column): _classic_read = False _classic_write = False _type = 'many2many' - + def __init__(self, obj, rel, id1, id2, string='unknown', limit=None, **args): _column.__init__(self, string=string, **args) self._obj = obj @@ -435,15 +456,15 @@ class many2many(_column): def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None): if not context: - context={} + context = {} if not values: - values={} + values = {} res = {} if not ids: return res for id in ids: res[id] = [] - ids_s = ','.join(map(str,ids)) + 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) @@ -463,39 +484,39 @@ class many2many(_column): def set(self, cr, obj, id, name, values, user=None, context=None): if not context: - context={} + context = {} if not values: return obj = obj.pool.get(self._obj) for act in values: - if act[0]==0: + if act[0] == 0: idnew = obj.create(cr, user, act[2]) - cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%d,%d)', (id,idnew)) - elif act[0]==1: - obj.write(cr, user, [act[1]] , act[2], context=context) - elif act[0]==2: + cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%d,%d)', (id, idnew)) + elif act[0] == 1: + obj.write(cr, user, [act[1]], act[2], context=context) + elif act[0] == 2: obj.unlink(cr, user, [act[1]], context=context) - elif act[0]==3: - cr.execute('delete from '+self._rel+' where ' + self._id1 + '=%d and '+ self._id2 + '=%d', (id,act[1])) - elif act[0]==4: - cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%d,%d)', (id,act[1])) - elif act[0]==5: + elif act[0] == 3: + cr.execute('delete from '+self._rel+' where ' + self._id1 + '=%d and '+ self._id2 + '=%d', (id, act[1])) + elif act[0] == 4: + cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%d,%d)', (id, act[1])) + elif act[0] == 5: cr.execute('update '+self._rel+' set '+self._id2+'=null where '+self._id2+'=%d', (id,)) - elif act[0]==6: + elif act[0] == 6: - d1, d2 = obj.pool.get('ir.rule').domain_get(cr, user, obj._name) + d1, d2 = obj.pool.get('ir.rule').domain_get(cr, user, obj._name) if d1: d1 = ' and '+d1 - cr.execute('delete from '+self._rel+' where '+self._id1+'=%d AND '+self._id2+' IN (SELECT '+self._rel+'.'+self._id2+' FROM '+self._rel+', '+obj._table+' WHERE '+self._rel+'.'+self._id1+'=%d AND '+self._rel+'.'+self._id2+' = '+obj._table+'.id '+ d1 +')', [id, id]+d2 ) + cr.execute('delete from '+self._rel+' where '+self._id1+'=%d AND '+self._id2+' IN (SELECT '+self._rel+'.'+self._id2+' FROM '+self._rel+', '+obj._table+' WHERE '+self._rel+'.'+self._id1+'=%d AND '+self._rel+'.'+self._id2+' = '+obj._table+'.id '+ d1 +')', [id, id]+d2) - for act_nbr in act[2]: + for act_nbr in act[2]: cr.execute('insert into '+self._rel+' ('+self._id1+','+self._id2+') values (%d, %d)', (id, act_nbr)) # # 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) + return obj.pool.get(self._obj).search(cr, uid, args+self._domain+[('name', operator, value)], offset, limit) def get_memory(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None): result = {} @@ -508,32 +529,31 @@ class many2many(_column): return for act in values: # TODO: use constants instead of these magic numbers - if act[0]==0: + if act[0] == 0: raise _('Not Implemented') - elif act[0]==1: + elif act[0] == 1: raise _('Not Implemented') - elif act[0]==2: + elif act[0] == 2: raise _('Not Implemented') - elif act[0]==3: + elif act[0] == 3: raise _('Not Implemented') - elif act[0]==4: + elif act[0] == 4: raise _('Not Implemented') - elif act[0]==5: + elif act[0] == 5: raise _('Not Implemented') - elif act[0]==6: + elif act[0] == 6: obj.datas[id][name] = act[2] # --------------------------------------------------------- # Function fields # --------------------------------------------------------- - class function(_column): _classic_read = False _classic_write = False _type = 'function' _properties = True - + def __init__(self, fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None, method=False, store=False, **args): _column.__init__(self, **args) self._obj = obj @@ -553,7 +573,7 @@ class function(_column): self._symbol_c = '%f' self._symbol_f = lambda x: __builtin__.float(x or 0.0) self._symbol_set = (self._symbol_c, self._symbol_f) - + def search(self, cr, uid, obj, name, args): if not self._fnct_search: #CHECKME: should raise an exception @@ -562,9 +582,9 @@ class function(_column): def get(self, cr, obj, ids, name, user=None, context=None, values=None): if not context: - context={} + context = {} if not values: - values={} + values = {} res = {} table = obj._table if self._method: @@ -575,10 +595,11 @@ class function(_column): def set(self, cr, obj, id, name, value, user=None, context=None): if not context: - context={} + context = {} if self._fnct_inv: self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context) + # --------------------------------------------------------- # Serialized fields # --------------------------------------------------------- @@ -596,7 +617,7 @@ class property(function): def _fnct_write(self, obj, cr, uid, id, prop, id_val, val, context=None): if not context: - context={} + context = {} (obj_dest,) = val definition_id = self._field_get(cr, uid, obj._name, prop) @@ -629,7 +650,7 @@ class property(function): def _fnct_read(self, obj, cr, uid, ids, prop, val, context=None): if not context: - context={} + context = {} property = obj.pool.get('ir.property') definition_id = self._field_get(cr, uid, obj._name, prop) @@ -646,7 +667,7 @@ class property(function): res = {} for id in ids: - res[id]= default_val + res[id] = default_val for prop in property.browse(cr, uid, nids): res[int(prop.res_id.split(',')[1])] = (prop.value and \ int(prop.value.split(',')[1])) or False diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 0f0ec2f70c7..ffb2fbe16c6 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -49,21 +49,18 @@ import time import types from xml import dom, xpath -from xml.parsers import expat import string -import pooler -import psycopg import netsvc import re import fields -import ir import tools from tools.config import config regex_order = re.compile('^([a-zA-Z0-9_]+( desc)?( asc)?,?)+$', re.I) + def intersect(la, lb): return filter(lambda x: x in lb, la) @@ -74,11 +71,12 @@ class except_orm(Exception): self.value = value self.args = (name, value) + # Readonly python database object browser class browse_null(object): def __init__(self): - self.id=False + self.id = False def __getitem__(self, name): return False @@ -92,6 +90,7 @@ class browse_null(object): def __nonzero__(self): return False + # # TODO: execute an object method on browse_record_list # @@ -125,13 +124,13 @@ class browse_record(object): cache.setdefault(table._name, {}) self._data = cache[table._name] if not id in self._data: - self._data[id] = {'id':id} + self._data[id] = {'id': id} self._cache = cache def __getitem__(self, name): if name == 'id': return self._id - if not self._data[self._id].has_key(name): + if not name in self._data[self._id]: # build the list of fields we will fetch # fetch the definition of the field which was asked for @@ -140,7 +139,7 @@ class browse_record(object): elif name in self._table._inherit_fields: col = self._table._inherit_fields[name][2] elif hasattr(self._table, name): - if isinstance( getattr(self._table, name), (types.MethodType,types.LambdaType,types.FunctionType)): + 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: return getattr(self._table, name) @@ -159,13 +158,13 @@ class browse_record(object): ffields += filter(lambda x: x[1]._classic_write, inherits) # otherwise we fetch only that field else: - ffields = [(name,col)] - ids = filter(lambda id: not self._data[id].has_key(name), self._data.keys()) + ffields = [(name, col)] + ids = filter(lambda id: not name in self._data[id], self._data.keys()) # read the data fffields = map(lambda x: x[0], ffields) datas = self._table.read(self._cr, self._uid, ids, fffields, context=self._context, load="_classic_write") if self._fields_process: - for n,f in ffields: + for n, f in ffields: if f._type in self._fields_process: for d in datas: d[n] = self._fields_process[f._type](d[n]) @@ -174,11 +173,11 @@ class browse_record(object): # create browse records for 'remote' objects for data in datas: - for n,f in ffields: + for n, f in ffields: if f._type in ('many2one', 'one2one'): if data[n]: obj = self._table.pool.get(f._obj) - compids=False + compids = False if not f._classic_write: ids2 = data[n][0] else: @@ -190,7 +189,7 @@ class browse_record(object): else: data[n] = browse_null() 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) + 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) return self._data[self._id][name] @@ -234,38 +233,38 @@ def get_pg_type(f): ''' type_dict = { - fields.boolean:'bool', - fields.integer:'int4', - fields.text:'text', - fields.date:'date', - fields.time:'time', - fields.datetime:'timestamp', - fields.binary:'bytea', - fields.many2one:'int4', + fields.boolean: 'bool', + fields.integer: 'int4', + fields.text: 'text', + fields.date: 'date', + fields.time: 'time', + fields.datetime: 'timestamp', + fields.binary: 'bytea', + fields.many2one: 'int4', } - if type_dict.has_key(type(f)): + if type(f) in type_dict: f_type = (type_dict[type(f)], type_dict[type(f)]) elif isinstance(f, fields.float): if f.digits: - f_type = ('numeric', 'NUMERIC(%d,%d)' % (f.digits[0],f.digits[1])) + f_type = ('numeric', 'NUMERIC(%d,%d)' % (f.digits[0], f.digits[1])) else: f_type = ('float8', 'DOUBLE PRECISION') elif isinstance(f, (fields.char, fields.reference)): f_type = ('varchar', 'VARCHAR(%d)' % (f.size,)) elif isinstance(f, fields.selection): if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)): - f_size = reduce(lambda x,y: max(x,len(y[0])), f.selection, f.size or 16) + f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16) elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int): f_size = -1 else: - f_size = (hasattr(f,'size') and f.size) or 16 + f_size = (hasattr(f, 'size') and f.size) or 16 if f_size == -1: f_type = ('int4', 'INTEGER') else: f_type = ('varchar', 'VARCHAR(%d)' % f_size) - elif isinstance(f, fields.function) and type_dict.has_key(eval('fields.'+(f._type))): - t=eval('fields.'+(f._type)) + elif isinstance(f, fields.function) and eval('fields.'+(f._type)) in type_dict: + t = eval('fields.'+(f._type)) f_type = (type_dict[t], type_dict[t]) elif isinstance(f, fields.function) and f._type == 'float': f_type = ('float8', 'DOUBLE PRECISION') @@ -277,6 +276,7 @@ def get_pg_type(f): f_type = None return f_type + class orm_template(object): _name = None _columns = {} @@ -297,10 +297,10 @@ class orm_template(object): # reference model in order to have a description of its fonctionnality in custom_report cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',)) id = cr.fetchone()[0] - cr.execute("INSERT INTO ir_model (id,model, name, info) VALUES (%s, %s, %s, %s)", (id,self._name, self._description, self.__doc__)) + cr.execute("INSERT INTO ir_model (id,model, name, info) VALUES (%s, %s, %s, %s)", (id, self._name, self._description, self.__doc__)) if 'module' in context: cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \ - ( 'model_'+self._table, context['module'], 'ir.model', id) + ('model_'+self._table, context['module'], 'ir.model', id) ) cr.commit() @@ -312,25 +312,25 @@ class orm_template(object): cr.execute("SELECT id FROM ir_model WHERE model='%s'" % self._name) model_id = cr.fetchone()[0] - for (k,f) in self._columns.items(): + for (k, f) in self._columns.items(): vals = { - 'model_id':model_id, - 'model':self._name, - 'name':k, - 'field_description':f.string.replace("'", " "), - 'ttype':f._type, - 'relate':(f.relate and 1) or 0, - 'relation':f._obj or 'NULL', - 'group_name':f.group_name or '', - 'view_load':(f.view_load and 1) or 0, - 'select_level':str(f.select or 0) + 'model_id': model_id, + 'model': self._name, + 'name': k, + 'field_description': f.string.replace("'", " "), + 'ttype': f._type, + 'relate': (f.relate and 1) or 0, + 'relation': f._obj or 'NULL', + 'group_name': f.group_name or '', + 'view_load': (f.view_load and 1) or 0, + 'select_level': str(f.select or 0) } if k not in cols: cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',)) id = cr.fetchone()[0] vals['id'] = id cr.execute("""INSERT INTO ir_model_fields ( - id, model_id, model, name, field_description, ttype, + id, model_id, model, name, field_description, ttype, relate,relation,group_name,view_load,state,select_level ) VALUES ( %d,%s,%s,%s,%s,%s, %s,%s,%s,%s,%s, %s @@ -341,21 +341,21 @@ class orm_template(object): )) if 'module' in context: cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \ - ( ('field_'+self._table+'_'+k)[:64], context['module'], 'ir.model.fields', id) + (('field_'+self._table+'_'+k)[:64], context['module'], 'ir.model.fields', id) ) else: - for key,val in vals.items(): - if cols[k][key]<>vals[key]: + for key, val in vals.items(): + if cols[k][key] != vals[key]: print 'Different', cols[k][key], vals[key], k, key, self._name - cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'],vals['model'],vals['name'])) + cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name'])) cr.commit() cr.execute("""UPDATE ir_model_fields SET model_id=%s, field_description=%s, ttype=%s, relate=%s, relation=%s, group_name=%s, view_load=%s, select_level=%s WHERE model=%s AND name=%s""", ( - vals['model_id'],vals['field_description'],vals['ttype'],bool(vals['relate']), - vals['relation'], vals['group_name'], bool(vals['view_load']), + vals['model_id'], vals['field_description'], vals['ttype'], bool(vals['relate']), + vals['relation'], vals['group_name'], bool(vals['view_load']), vals['select_level'], vals['model'], vals['name'] )) continue @@ -368,19 +368,19 @@ class orm_template(object): if not self._description: self._description = self._name if not self._table: - self._table=self._name.replace('.','_') + self._table = self._name.replace('.', '_') def browse(self, cr, uid, select, context=None, list_class=None, fields_process={}): if not context: - context={} + context = {} self._list_class = list_class or browse_record_list cache = {} # need to accepts ints and longs because ids coming from a method # launched by button in the interface have a type long... if isinstance(select, (int, long)): - return browse_record(cr,uid,select,self,cache, context=context, list_class=self._list_class, fields_process=fields_process) - elif isinstance(select,list): - return self._list_class([browse_record(cr,uid,id,self,cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context) + return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) + elif isinstance(select, list): + return self._list_class([browse_record(cr, uid, id, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context) else: return browse_null() @@ -393,7 +393,7 @@ class orm_template(object): if f: r = row i = 0 - while i=len(line): - raise Exception, _('Please check that all your lines have %d columns.') % (len(fields),) + if i >= len(line): + raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),)) field = fields[i] if field == ["id"]: - data_id= line[i] + data_id = line[i] continue if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'): res_id = False @@ -471,7 +472,7 @@ class orm_template(object): if res_id2: res_id.append(res_id2) if len(res_id): - res_id=[(6,0,res_id)] + res_id = [(6, 0, res_id)] else: if '.' in line[i]: module, xml_id = line[i].rsplit('.', 1) @@ -491,9 +492,9 @@ class orm_template(object): if (len(field) == len(prefix)+1) and \ (prefix == field[0:len(prefix)]): if fields_def[field[len(prefix)]]['type'] == 'integer': - res =line[i] and int(line[i]) + res = line[i] and int(line[i]) elif fields_def[field[len(prefix)]]['type'] == 'float': - res =line[i] and float(line[i]) + res = line[i] and float(line[i]) elif fields_def[field[len(prefix)]]['type'] == 'selection': res = False if isinstance(fields_def[field[len(prefix)]]['selection'], @@ -540,7 +541,7 @@ class orm_template(object): else: res.append(res3) if len(res): - res= [(6,0,res)] + res = [(6, 0, res)] else: res = line[i] or False row[field[len(prefix)]] = res @@ -558,9 +559,9 @@ class orm_template(object): (newrow, max2, w2, translate2, data_id2) = res nbrmax = max(nbrmax, max2) warning = warning + w2 - reduce(lambda x,y: x and y, newrow) + reduce(lambda x, y: x and y, newrow) row[field] = (reduce(lambda x, y: x or y, newrow.values()) and \ - [(0,0,newrow)]) or [] + [(0, 0, newrow)]) or [] i = max2 while (position+i) 'ir_actions': + if ref != 'ir_actions': cr.execute("ALTER TABLE \"%s\" ADD FOREIGN KEY (\"%s\") REFERENCES \"%s\" ON DELETE %s" % (self._table, k, ref, f.ondelete)) if f.select: cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (self._table, k, self._table, k)) @@ -1388,15 +1391,15 @@ class orm(orm_template): # We update varchar size, otherwise, we keep DB size # to avoid truncated string... if f_pg_size < f.size: - cr.execute("ALTER TABLE \"%s\" RENAME COLUMN \"%s\" TO temp_change_size" % (self._table,k)) - cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" VARCHAR(%d)" % (self._table,k,f.size)) - cr.execute("UPDATE \"%s\" SET \"%s\"=temp_change_size::VARCHAR(%d)" % (self._table,k,f.size)) + cr.execute("ALTER TABLE \"%s\" RENAME COLUMN \"%s\" TO temp_change_size" % (self._table, k)) + cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" VARCHAR(%d)" % (self._table, k, f.size)) + cr.execute("UPDATE \"%s\" SET \"%s\"=temp_change_size::VARCHAR(%d)" % (self._table, k, f.size)) cr.execute("ALTER TABLE \"%s\" DROP COLUMN temp_change_size" % (self._table,)) cr.commit() # if the field is required and hasn't got a NOT NULL constraint if f.required and f_pg_notnull == 0: # set the field to the default value if any - if self._defaults.has_key(k): + if k in self._defaults: default = self._defaults[k](self, cr, 1, {}) if not (default is False): cr.execute("UPDATE \"%s\" SET \"%s\"='%s' WHERE %s is NULL" % (self._table, k, default, k)) @@ -1409,7 +1412,7 @@ class orm(orm_template): logger.notifyChannel('init', netsvc.LOG_WARNING, 'unable to set a NOT NULL constraint on column %s of the %s table !\nIf you want to have it, you should update the records and execute manually:\nALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % (k, self._table, self._table, k)) cr.commit() elif not f.required and f_pg_notnull == 1: - cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" DROP NOT NULL" % (self._table,k)) + cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" DROP NOT NULL" % (self._table, k)) cr.commit() cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = '%s_%s_index' and tablename = '%s'" % (self._table, k, self._table)) res = cr.dictfetchall() @@ -1456,26 +1459,26 @@ class orm(orm_template): cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table) create = not bool(cr.fetchone()) - for (key,con,_) in self._sql_constraints: + for (key, con, _) in self._sql_constraints: cr.execute("SELECT conname FROM pg_constraint where conname='%s_%s'" % (self._table, key)) if not cr.dictfetchall(): try: - cr.execute('alter table \"%s\" add constraint \"%s_%s\" %s' % (self._table,self._table,key, con,)) + cr.execute('alter table \"%s\" add constraint \"%s_%s\" %s' % (self._table, self._table, key, con,)) cr.commit() except: - logger.notifyChannel('init', netsvc.LOG_WARNING, 'unable to add \'%s\' constraint on table %s !\n If you want to have it, you should update the records and execute manually:\nALTER table %s ADD CONSTRAINT %s_%s %s' % (con, self._table, self._table,self._table,key, con,)) + logger.notifyChannel('init', netsvc.LOG_WARNING, 'unable to add \'%s\' constraint on table %s !\n If you want to have it, you should update the records and execute manually:\nALTER table %s ADD CONSTRAINT %s_%s %s' % (con, self._table, self._table, self._table, key, con,)) if create: - if hasattr(self,"_sql"): + if hasattr(self, "_sql"): for line in self._sql.split(';'): - line2 = line.replace('\n','').strip() + line2 = line.replace('\n', '').strip() if line2: cr.execute(line2) cr.commit() def __init__(self, cr): super(orm, self).__init__(cr) - f=filter(lambda a: isinstance(self._columns[a], fields.function) and self._columns[a].store, self._columns) + f = filter(lambda a: isinstance(self._columns[a], fields.function) and self._columns[a].store, self._columns) if f: list_store = [] tuple_store = () @@ -1484,29 +1487,29 @@ class orm(orm_template): if not self._columns[store_field].store == True: dict_store = self._columns[store_field].store key = dict_store.keys() - list_data=[] + list_data = [] for i in key: - tuple_store=self._name,store_field,self._columns[store_field]._fnct.__name__,tuple(dict_store[i][0]),dict_store[i][1],i + tuple_store = self._name, store_field, self._columns[store_field]._fnct.__name__, tuple(dict_store[i][0]), dict_store[i][1], i list_data.append(tuple_store) #tuple_store=self._name,store_field,self._columns[store_field]._fnct.__name__,tuple(dict_store[key[0]][0]),dict_store[key[0]][1] for l in list_data: - list_store=[] + list_store = [] if l[5] in self.pool._store_function.keys(): self.pool._store_function[l[5]].append(l) temp_list = list(set(self.pool._store_function[l[5]])) - self.pool._store_function[l[5]]=temp_list + self.pool._store_function[l[5]] = temp_list else: list_store.append(l) - self.pool._store_function[l[5]]=list_store + self.pool._store_function[l[5]] = list_store - for (key,_,msg) in self._sql_constraints: + for (key, _, msg) in self._sql_constraints: self.pool._sql_error[self._table+'_'+key] = msg # Load manual fields - cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state','ir.model.fields')) + cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields')) if cr.fetchone(): - cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name,'manual')) + cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual')) for field in cr.dictfetchall(): if field['name'] in self._columns: continue @@ -1539,7 +1542,7 @@ class orm(orm_template): def default_get(self, cr, uid, fields_list, context=None): if not context: - context={} + context = {} value = {} # get the default values for the inherited fields for t in self._inherits.keys(): @@ -1551,7 +1554,7 @@ class orm(orm_template): if f in self._defaults: value[f] = self._defaults[f](self, cr, uid, context) fld_def = ((f in self._columns) and self._columns[f]) \ - or ((f in self._inherit_fields ) and self._inherit_fields[f][2]) \ + or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \ or False if isinstance(fld_def, fields.property): property_obj = self.pool.get('ir.property') @@ -1615,51 +1618,51 @@ class orm(orm_template): for table in self._inherits: res.update(self.pool.get(table)._inherit_fields) for col in self.pool.get(table)._columns.keys(): - res[col]=(table, self._inherits[table], self.pool.get(table)._columns[col]) + res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col]) for col in self.pool.get(table)._inherit_fields.keys(): - res[col]=(table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2]) - self._inherit_fields=res + res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2]) + self._inherit_fields = res self._inherits_reload_src() def fields_get(self, cr, user, fields=None, context=None): - read_access= self.pool.get('ir.model.access').check(cr, user, self._name, 'write', raise_exception=False) + read_access = self.pool.get('ir.model.access').check(cr, user, self._name, 'write', raise_exception=False) return super(orm, self).fields_get(cr, user, fields, context, read_access) def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'): if not context: - context={} + context = {} self.pool.get('ir.model.access').check(cr, user, self._name, 'read') if not fields: fields = self._columns.keys() + self._inherit_fields.keys() select = ids if isinstance(ids, (int, long)): select = [ids] - result = self._read_flat(cr, user, select, fields, context, load) + result = self._read_flat(cr, user, select, fields, context, load) for r in result: - for key,v in r.items(): + for key, v in r.items(): if v == None: - r[key]=False + r[key] = False if isinstance(ids, (int, long)): return result[0] return result def _read_flat(self, cr, user, ids, fields, context=None, load='_classic_read'): if not context: - context={} + context = {} if not ids: return [] - if fields==None: + if fields == None: fields = self._columns.keys() # construct a clause for the rules : d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name) # all inherited fields + all non inherited fields for which the attribute whose name is in load is True - fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x],'_classic_write'), fields) + self._inherits.values() + fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x], '_classic_write'), fields) + self._inherits.values() res = [] - if len(fields_pre) : + if len(fields_pre): fields_pre2 = map(lambda x: (x in ('create_date', 'write_date')) and ('date_trunc(\'second\', '+x+') as '+x) or '"'+x+'"', fields_pre) for i in range(0, len(ids), cr.IN_MAX): sub_ids = ids[i:i+cr.IN_MAX] @@ -1667,7 +1670,7 @@ class orm(orm_template): cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) AND %s ORDER BY %s' % \ (','.join(fields_pre2 + ['id']), self._table, ','.join([str(x) for x in sub_ids]), d1, - self._order),d2) + self._order), d2) if not cr.rowcount == len({}.fromkeys(sub_ids)): raise except_orm(_('AccessError'), _('You try to bypass an access rule (Document type: %s).') % self._description) @@ -1761,22 +1764,22 @@ class orm(orm_template): def perm_read(self, cr, user, ids, context=None, details=True): if not context: - context={} + context = {} if not ids: return [] fields = '' if self._log_access: - fields =', u.create_uid, u.create_date, u.write_uid, u.write_date' + fields = ', u.create_uid, u.create_date, u.write_uid, u.write_date' if isinstance(ids, (int, long)): ids_str = str(ids) else: - ids_str = string.join(map(lambda x:str(x), ids),',') + ids_str = string.join(map(lambda x: str(x), ids), ',') cr.execute('select u.id'+fields+' from "'+self._table+'" u where u.id in ('+ids_str+')') res = cr.dictfetchall() for r in res: for key in r: r[key] = r[key] or False - if key in ('write_uid','create_uid','uid') and details: + if key in ('write_uid', 'create_uid', 'uid') and details: if r[key]: r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0] if isinstance(ids, (int, long)): @@ -1785,7 +1788,7 @@ class orm(orm_template): def unlink(self, cr, uid, ids, context=None): if not context: - context={} + context = {} if not ids: return True if isinstance(ids, (int, long)): @@ -1798,18 +1801,18 @@ class orm(orm_template): id_change = [] for tuple_fn in list_store: for id in ids: - id_change.append(self._store_get_ids(cr, uid, id,tuple_fn, context)[0]) - fn_data = id_change,tuple_fn + id_change.append(self._store_get_ids(cr, uid, id, tuple_fn, context)[0]) + fn_data = id_change, tuple_fn fn_list.append(fn_data) - delta= context.get('read_delta',False) + delta = context.get('read_delta', False) if delta and self._log_access: for i in range(0, len(ids), cr.IN_MAX): sub_ids = ids[i:i+cr.IN_MAX] cr.execute("select (now() - min(write_date)) <= '%s'::interval " \ "from \"%s\" where id in (%s)" % - (delta, self._table, ",".join(map(str, sub_ids))) ) - res= cr.fetchone() + (delta, self._table, ",".join(map(str, sub_ids)))) + res = cr.fetchone() if res and res[0]: raise except_orm(_('ConcurrencyException'), _('This record was modified in the meanwhile')) @@ -1832,7 +1835,7 @@ class orm(orm_template): for i in range(0, len(ids), cr.IN_MAX): sub_ids = ids[i:i+cr.IN_MAX] - str_d = string.join(('%d',)*len(sub_ids),',') + str_d = string.join(('%d',)*len(sub_ids), ',') if d1: cr.execute('SELECT id FROM "'+self._table+'" ' \ 'WHERE id IN ('+str_d+')'+d1, sub_ids+d2) @@ -1848,8 +1851,8 @@ class orm(orm_template): cr.execute('delete from "'+self._table+'" ' \ 'where id in ('+str_d+')', sub_ids) if fn_list: - for ids,tuple_fn in fn_list: - self._store_set_values(cr, uid, ids,tuple_fn,id_change, context) + for ids, tuple_fn in fn_list: + self._store_set_values(cr, uid, ids, tuple_fn, id_change, context) return True @@ -1888,19 +1891,19 @@ class orm(orm_template): vals.pop(field) if not context: - context={} + context = {} if not ids: return True if isinstance(ids, (int, long)): ids = [ids] - delta= context.get('read_delta',False) + delta = context.get('read_delta', False) if delta and self._log_access: for i in range(0, len(ids), cr.IN_MAX): sub_ids = ids[i:i+cr.IN_MAX] cr.execute("select (now() - min(write_date)) <= '%s'::interval " \ "from %s where id in (%s)" % - (delta,self._table, ",".join(map(str, sub_ids)))) - res= cr.fetchone() + (delta, self._table, ",".join(map(str, sub_ids)))) + res = cr.fetchone() if res and res[0]: for field in vals: if field in self._columns and self._columns[field]._classic_write: @@ -1911,10 +1914,10 @@ class orm(orm_template): #for v in self._inherits.values(): # assert v not in vals, (v, vals) - upd0=[] - upd1=[] - upd_todo=[] - updend=[] + upd0 = [] + upd1 = [] + upd_todo = [] + updend = [] direct = [] totranslate = context.get('lang', False) and (context['lang'] != 'en_US') for field in vals: @@ -1960,7 +1963,7 @@ class orm(orm_template): for i in range(0, len(ids), cr.IN_MAX): sub_ids = ids[i:i+cr.IN_MAX] - ids_str = string.join(map(str, sub_ids),',') + ids_str = string.join(map(str, sub_ids), ',') if d1: cr.execute('SELECT id FROM "'+self._table+'" ' \ 'WHERE id IN ('+ids_str+')'+d1, d2) @@ -1975,19 +1978,19 @@ class orm(orm_template): _('You try to write on an record that doesn\'t exist ' \ '(Document type: %s).') % self._description) if d1: - cr.execute('update "'+self._table+'" set '+string.join(upd0,',')+' ' \ + cr.execute('update "'+self._table+'" set '+string.join(upd0, ',')+' ' \ 'where id in ('+ids_str+')'+d1, upd1+ d2) else: - cr.execute('update "'+self._table+'" set '+string.join(upd0,',')+' ' \ + cr.execute('update "'+self._table+'" set '+string.join(upd0, ',')+' ' \ 'where id in ('+ids_str+')', upd1) if totranslate: for f in direct: if self._columns[f].translate: - self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f,'model', context['lang'], ids,vals[f]) + self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f]) # call the 'set' method of fields which are not classic_write - upd_todo.sort(lambda x,y: self._columns[x].priority-self._columns[y].priority) + upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority) for field in upd_todo: for id in ids: self._columns[field].set(cr, self, id, field, vals[field], user, context=context) @@ -1997,20 +2000,20 @@ class orm(orm_template): nids = [] for i in range(0, len(ids), cr.IN_MAX): sub_ids = ids[i:i+cr.IN_MAX] - ids_str = string.join(map(str, sub_ids),',') + ids_str = string.join(map(str, sub_ids), ',') cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \ 'where id in ('+ids_str+')', upd1) nids.extend([x[0] for x in cr.fetchall()]) v = {} for val in updend: - if self._inherit_fields[val][0]==table: - v[val]=vals[val] + if self._inherit_fields[val][0] == table: + v[val] = vals[val] self.pool.get(table).write(cr, user, nids, v, context) self._validate(cr, user, ids, context) - if context.has_key('read_delta'): + if 'read_delta' in context: del context['read_delta'] wf_service = netsvc.LocalService("workflow") @@ -2029,8 +2032,8 @@ class orm(orm_template): flag = True break if flag: - id_change = self._store_get_ids(cr, user, ids[0],tuple_fn, context) - self._store_set_values(cr, user, ids[0],tuple_fn,id_change, context) + id_change = self._store_get_ids(cr, user, ids[0], tuple_fn, context) + self._store_set_values(cr, user, ids[0], tuple_fn, id_change, context) return True @@ -2044,13 +2047,13 @@ class orm(orm_template): vals = dictionary of the form {'field_name':field_value, ...} """ if not context: - context={} + context = {} self.pool.get('ir.model.access').check(cr, user, self._name, 'create') default = [] avoid_table = [] - for (t,c) in self._inherits.items(): + for (t, c) in self._inherits.items(): if c in vals: avoid_table.append(t) for f in self._columns.keys(): # + self._inherit_fields.keys(): @@ -2073,7 +2076,7 @@ class orm(orm_template): for v in vals.keys(): if v in self._inherit_fields: - (table,col,col_detail) = self._inherit_fields[v] + (table, col, col_detail) = self._inherit_fields[v] tocreate[table][v] = vals[v] del vals[v] @@ -2087,8 +2090,8 @@ class orm(orm_template): for field in vals: if self._columns[field]._classic_write: - upd0=upd0+',"'+field+'"' - upd1=upd1+','+self._columns[field]._symbol_set[0] + upd0 = upd0 + ',"' + field + '"' + upd1 = upd1 + ',' + self._columns[field]._symbol_set[0] upd2.append(self._columns[field]._symbol_set[1](vals[field])) else: upd_todo.append(field) @@ -2115,7 +2118,7 @@ class orm(orm_template): upd1 += ',%d,now()' upd2.append(user) cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2)) - upd_todo.sort(lambda x,y: self._columns[x].priority-self._columns[y].priority) + upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority) for field in upd_todo: self._columns[field].set(cr, self, id_new, field, vals[field], user, context) @@ -2127,24 +2130,24 @@ class orm(orm_template): if self._name in self.pool._store_function.keys(): list_store = self.pool._store_function[self._name] for tuple_fn in list_store: - id_change = self._store_get_ids(cr, user, id_new,tuple_fn, context) - self._store_set_values(cr, user, id_new,tuple_fn,id_change, context) + id_change = self._store_get_ids(cr, user, id_new, tuple_fn, context) + self._store_set_values(cr, user, id_new, tuple_fn, id_change, context) return id_new - def _store_get_ids(self,cr, uid, ids,tuple_fn, context): - parent_id=getattr(self.pool.get(tuple_fn[0]),tuple_fn[4].func_name)(cr,uid,[ids]) + def _store_get_ids(self, cr, uid, ids, tuple_fn, context): + parent_id = getattr(self.pool.get(tuple_fn[0]), tuple_fn[4].func_name)(cr, uid, [ids]) return parent_id - def _store_set_values(self,cr,uid,ids,tuple_fn,parent_id,context): + def _store_set_values(self, cr, uid, ids, tuple_fn, parent_id, context): name = tuple_fn[1] table = tuple_fn[0] - args={} - vals_tot=getattr(self.pool.get(table),tuple_fn[2])(cr, uid, parent_id ,name, args, context) + args = {} + vals_tot = getattr(self.pool.get(table), tuple_fn[2])(cr, uid, parent_id, name, args, context) write_dict = {} for id in vals_tot.keys(): - write_dict[name]=vals_tot[id] - self.pool.get(table).write(cr,uid,[id],write_dict) + write_dict[name] = vals_tot[id] + self.pool.get(table).write(cr, uid, [id], write_dict) return True def _update_function_stored(self, cr, user, ids, context=None): @@ -2153,10 +2156,10 @@ class orm(orm_template): f = filter(lambda a: isinstance(self._columns[a], fields.function) \ and self._columns[a].store, self._columns) if f: - result=self.read(cr, user, ids, fields=f, context=context) + result = self.read(cr, user, ids, fields=f, context=context) for res in result: - upd0=[] - upd1=[] + upd0 = [] + upd1 = [] for field in res: if field not in f: continue @@ -2179,7 +2182,7 @@ class orm(orm_template): # TODO: ameliorer avec NULL def _where_calc(self, cr, user, args, active_test=True, context=None): if not context: - context={} + context = {} args = args[:] # if the object has a field named 'active', filter out all inactive # records unless they were explicitely asked for @@ -2196,7 +2199,7 @@ class orm(orm_template): else: qu1, qu2, tables = [], [], ['"%s"' % self._table] - return (qu1,qu2,tables) + return (qu1, qu2, tables) def _check_qorder(self, word): if not regex_order.match(word): @@ -2208,10 +2211,10 @@ class orm(orm_template): if not context: context = {} # compute the where, order by, limit and offset clauses - (qu1,qu2,tables) = self._where_calc(cr, user, args, context=context) + (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context) if len(qu1): - qu1 = ' where '+string.join(qu1,' and ') + qu1 = ' where '+string.join(qu1, ' and ') else: qu1 = '' @@ -2244,15 +2247,15 @@ class orm(orm_template): # a char field def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None): if not args: - args=[] + args = [] if field in self._inherit_fields: - return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid,field,value,args,offset,limit) + return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit) else: return self._columns[field].search(cr, self, args, field, value, offset, limit, uid) def name_get(self, cr, user, ids, context=None): if not context: - context={} + context = {} if not ids: return [] if isinstance(ids, (int, long)): @@ -2262,19 +2265,19 @@ class orm(orm_template): def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=80): if not args: - args=[] + args = [] if not context: - context={} - args=args[:] + context = {} + args = args[:] if name: - args += [(self._rec_name,operator,name)] + args += [(self._rec_name, operator, name)] ids = self.search(cr, user, args, limit=limit, context=context) res = self.name_get(cr, user, ids, context) return res def copy(self, cr, uid, id, default=None, context=None): if not context: - context={} + context = {} if not default: default = {} if 'state' not in default: @@ -2315,7 +2318,7 @@ class orm(orm_template): def read_string(self, cr, uid, id, langs, fields=None, context=None): if not context: - context={} + context = {} res = {} res2 = {} self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read') @@ -2327,22 +2330,22 @@ class orm(orm_template): if f in self._columns: res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang) if res_trans: - res[lang][f]=res_trans + res[lang][f] = res_trans else: - res[lang][f]=self._columns[f].string + res[lang][f] = self._columns[f].string for table in self._inherits: cols = intersect(self._inherit_fields.keys(), fields) res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context) for lang in res2: if lang in res: - res[lang]={'code': lang} + res[lang] = {'code': lang} for f in res2[lang]: - res[lang][f]=res2[lang][f] + res[lang][f] = res2[lang][f] return res def write_string(self, cr, uid, id, langs, vals, context=None): if not context: - context={} + context = {} self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write') for lang in langs: for field in vals: diff --git a/bin/osv/osv.py b/bin/osv/osv.py index 369f006c9df..7ce0c0e85b5 100644 --- a/bin/osv/osv.py +++ b/bin/osv/osv.py @@ -28,334 +28,2351 @@ ############################################################################### # -# OSV: Objects Services +# Object relationnal mapping to postgresql module +# . Hierarchical structure +# . Constraints consistency, validations +# . Object meta Data depends on its status +# . Optimised processing by complex query (multiple actions at once) +# . Default fields value +# . Permissions optimisation +# . Persistant object: DB postgresql +# . Datas conversions +# . Multi-level caching system +# . 2 different inheritancies +# . Fields: +# - classicals (varchar, integer, boolean, ...) +# - relations (one2many, many2one, many2many) +# - functions +# # -import orm +import time +import types +from xml import dom, xpath import netsvc -import pooler -import copy -import sys +import re -import psycopg -from netsvc import Logger, LOG_ERROR -from tools.misc import UpdateableDict +import fields +import tools -module_list = [] -module_class_list = {} -class_pool = {} +from tools.config import config -class except_osv(Exception): - def __init__(self, name, value, exc_type='warning'): +regex_order = re.compile('^([a-zA-Z0-9_]+( desc)?( asc)?,?)+$', re.I) + + +def intersect(la, lb): + return filter(lambda x: x in lb, la) + + +class except_orm(Exception): + def __init__(self, name, value): self.name = name - self.exc_type = exc_type self.value = value - self.args = (exc_type,name) + self.args = (name, value) -class osv_pool(netsvc.Service): + +# Readonly python database object browser +class browse_null(object): def __init__(self): - self.obj_pool = {} - self.module_object_list = {} - self.created = [] - self._sql_error = {} - self._store_function = {} - netsvc.Service.__init__(self, 'object_proxy', audience='') - self.joinGroup('web-services') - self.exportMethod(self.exportedMethods) - self.exportMethod(self.obj_list) - self.exportMethod(self.exec_workflow) - self.exportMethod(self.execute) - self.exportMethod(self.execute_cr) + self.id = False - def execute_cr(self, cr, uid, obj, method, *args, **kw): - try: - object = pooler.get_pool(cr.dbname).get(obj) - if not object: - self.abortResponse(1, 'Object Error', 'warning', - 'Object %s doesn\'t exist' % str(obj)) - return getattr(object,method)(cr, uid, *args, **kw) - except orm.except_orm, inst: - self.abortResponse(1, inst.name, 'warning', inst.value) - except except_osv, inst: - self.abortResponse(1, inst.name, inst.exc_type, inst.value) - except psycopg.IntegrityError, inst: - for key in self._sql_error.keys(): - if key in inst[0]: - self.abortResponse(1, 'Constraint Error', 'warning', - self._sql_error[key]) - self.abortResponse(1, 'Integrity Error', 'warning', inst[0]) - except Exception, e: - import traceback - tb_s = reduce(lambda x, y: x+y, traceback.format_exception( - sys.exc_type, sys.exc_value, sys.exc_traceback)) - logger = Logger() - logger.notifyChannel("web-services", LOG_ERROR, - 'Exception in call: ' + tb_s) - raise + def __getitem__(self, name): + return False - def execute(self, db, uid, obj, method, *args, **kw): - db, pool = pooler.get_db_and_pool(db) - cr = db.cursor() - try: - try: - res = pool.execute_cr(cr, uid, obj, method, *args, **kw) - cr.commit() - except Exception: - cr.rollback() - raise - finally: - cr.close() - return res + def __int__(self): + return False - def exec_workflow_cr(self, cr, uid, obj, method, *args): - wf_service = netsvc.LocalService("workflow") - return wf_service.trg_validate(uid, obj, args[0], method, cr) + def __str__(self): + return '' - def exec_workflow(self, db, uid, obj, method, *args): - cr = pooler.get_db(db).cursor() - try: - try: - res = self.exec_workflow_cr(cr, uid, obj, method, *args) - cr.commit() - except orm.except_orm, inst: - cr.rollback() - self.abortResponse(1, inst.name, 'warning', inst.value) - except except_osv, inst: - cr.rollback() - self.abortResponse(1, inst.name, inst[0], inst.value) - finally: - cr.close() - return res - - def obj_list(self): - return self.obj_pool.keys() - - # adds a new object instance to the object pool. - # if it already existed, the instance is replaced - def add(self, name, obj_inst): - if self.obj_pool.has_key(name): - del self.obj_pool[name] - self.obj_pool[name] = obj_inst - - module = str(obj_inst.__class__)[6:] - module = module[:len(module)-1] - module = module.split('.')[0][2:] - self.module_object_list.setdefault(module, []).append(obj_inst) - - def get(self, name): - obj = self.obj_pool.get(name, None) -# We cannot uncomment this line because it breaks initialisation since objects do not initialize -# in the correct order and the ORM doesnt support correctly when some objets do not exist yet -# assert obj, "object %s does not exist !" % name - return obj - - #TODO: pass a list of modules to load - def instanciate(self, module, cr): -# print "module list:", module_list -# for module in module_list: - res = [] - class_list = module_class_list.get(module, []) -# if module not in self.module_object_list: -# print "%s class_list:" % module, class_list - for klass in class_list: - res.append(klass.createInstance(self, module, cr)) - return res -# else: -# print "skipping module", module - -#pooler.get_pool(cr.dbname) = osv_pool() - -class osv_memory(orm.orm_memory): - #__metaclass__ = inheritor - def __new__(cls): - module = str(cls)[6:] - module = module[:len(module)-1] - module = module.split('.')[0][2:] - if not hasattr(cls, '_module'): - cls._module = module - module_class_list.setdefault(cls._module, []).append(cls) - class_pool[cls._name] = cls - if module not in module_list: - module_list.append(cls._module) - return None - - # - # Goal: try to apply inheritancy at the instanciation level and - # put objects in the pool var - # - def createInstance(cls, pool, module, cr): - name = hasattr(cls,'_name') and cls._name or cls._inherit - parent_name = hasattr(cls, '_inherit') and cls._inherit - if parent_name: - print 'Inherit not supported in osv_memory object !' - obj = object.__new__(cls) - obj.__init__(pool, cr) - return obj - createInstance = classmethod(createInstance) - - def __init__(self, pool, cr): - pool.add(self._name, self) - self.pool = pool - orm.orm_memory.__init__(self, cr) + def __nonzero__(self): + return False +# +# TODO: execute an object method on browse_record_list +# +class browse_record_list(list): -class osv(orm.orm): - #__metaclass__ = inheritor - def __new__(cls): - module = str(cls)[6:] - module = module[:len(module)-1] - module = module.split('.')[0][2:] - if not hasattr(cls, '_module'): - cls._module = module - module_class_list.setdefault(cls._module, []).append(cls) - class_pool[cls._name] = cls - if module not in module_list: - module_list.append(cls._module) - return None + def __init__(self, lst, context=None): + if not context: + context = {} + super(browse_record_list, self).__init__(lst) + self.context = context - # - # Goal: try to apply inheritancy at the instanciation level and - # put objects in the pool var - # - def createInstance(cls, pool, module, cr): - parent_name = hasattr(cls, '_inherit') and cls._inherit - if parent_name: - parent_class = pool.get(parent_name).__class__ - assert parent_class, "parent class %s does not exist !" % parent_name - nattr = {} - for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'): - new = copy.copy(getattr(pool.get(parent_name), s)) - if hasattr(new, 'update'): - new.update(cls.__dict__.get(s, {})) + +class browse_record(object): + def __init__(self, cr, uid, id, table, cache, context=None, list_class = None, fields_process={}): + ''' + table : the object (inherited from orm) + context : a dictionnary with an optionnal context + ''' + if not context: + context = {} + assert id, _('Wrong ID for the browse record, got %s, expected an integer.') % str(id) + self._list_class = list_class or browse_record_list + self._cr = cr + self._uid = uid + self._id = id + self._table = table + self._table_name = self._table._name + self._context = context + self._fields_process = fields_process + + cache.setdefault(table._name, {}) + self._data = cache[table._name] + if not id in self._data: + self._data[id] = {'id': id} + self._cache = cache + + def __getitem__(self, name): + if name == 'id': + return self._id + if not name in self._data[self._id]: + # build the list of fields we will fetch + + # fetch the definition of the field which was asked for + if name in self._table._columns: + col = self._table._columns[name] + elif name in self._table._inherit_fields: + col = self._table._inherit_fields[name][2] + elif hasattr(self._table, 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: - new.extend(cls.__dict__.get(s, [])) - nattr[s] = new - name = hasattr(cls,'_name') and cls._name or cls._inherit - cls = type(name, (cls, parent_class), nattr) - obj = object.__new__(cls) - obj.__init__(pool, cr) - return obj - createInstance = classmethod(createInstance) + return getattr(self._table, name) + 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 - def __init__(self, pool, cr): - pool.add(self._name, self) - self.pool = pool - orm.orm.__init__(self, cr) + # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields + if col._classic_write: + # gen the list of "local" (ie not inherited) fields which are classic or many2one + ffields = filter(lambda x: x[1]._classic_write, self._table._columns.items()) + # gen the list of inherited fields + inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items()) + # complete the field list with the inherited fields which are classic or many2one + ffields += filter(lambda x: x[1]._classic_write, inherits) + # otherwise we fetch only that field + else: + ffields = [(name, col)] + ids = filter(lambda id: not name in self._data[id], self._data.keys()) + # read the data + fffields = map(lambda x: x[0], ffields) + datas = self._table.read(self._cr, self._uid, ids, fffields, context=self._context, load="_classic_write") + if self._fields_process: + for n, f in ffields: + if f._type in self._fields_process: + for d in datas: + d[n] = self._fields_process[f._type](d[n]) + d[n].set_value(d[n], self, f) -class Cacheable(object): - _cache = UpdateableDict() + # create browse records for 'remote' objects + for data in datas: + for n, f in ffields: + if f._type in ('many2one', 'one2one'): + if data[n]: + obj = self._table.pool.get(f._obj) + compids = False + if not f._classic_write: + ids2 = data[n][0] + else: + ids2 = data[n] + if ids2: + data[n] = browse_record(self._cr, self._uid, ids2, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) + else: + data[n] = browse_null() + else: + data[n] = browse_null() + 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) + return self._data[self._id][name] - def add(self, key, value): - self._cache[key] = value + def __getattr__(self, name): +# raise an AttributeError exception. + return self[name] - def invalidate(self, key): - del self._cache[key] + def __contains__(self, name): + return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name) - def get(self, key): - try: - w = self._cache[key] - return w - except KeyError: - return None + def __hasattr__(self, name): + return name in self + + def __int__(self): + return self._id + + def __str__(self): + return "browse_record(%s, %d)" % (self._table_name, self._id) + + def __eq__(self, other): + return (self._table_name, self._id) == (other._table_name, other._id) + + def __ne__(self, other): + return (self._table_name, self._id) != (other._table_name, other._id) + + # we need to define __unicode__ even though we've already defined __str__ + # because we have overridden __getattr__ + def __unicode__(self): + return unicode(str(self)) + + def __hash__(self): + return hash((self._table_name, self._id)) + + __repr__ = __str__ + + +def get_pg_type(f): + ''' + returns a tuple + (type returned by postgres when the column was created, type expression to create the column) + ''' + + type_dict = { + fields.boolean: 'bool', + fields.integer: 'int4', + fields.text: 'text', + fields.date: 'date', + fields.time: 'time', + fields.datetime: 'timestamp', + fields.binary: 'bytea', + fields.many2one: 'int4', + } + if type(f) in type_dict: + f_type = (type_dict[type(f)], type_dict[type(f)]) + elif isinstance(f, fields.float): + if f.digits: + f_type = ('numeric', 'NUMERIC(%d,%d)' % (f.digits[0], f.digits[1])) + else: + f_type = ('float8', 'DOUBLE PRECISION') + elif isinstance(f, (fields.char, fields.reference)): + f_type = ('varchar', 'VARCHAR(%d)' % (f.size,)) + elif isinstance(f, fields.selection): + if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)): + f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16) + elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int): + f_size = -1 + else: + f_size = (hasattr(f, 'size') and f.size) or 16 + + if f_size == -1: + f_type = ('int4', 'INTEGER') + else: + f_type = ('varchar', 'VARCHAR(%d)' % f_size) + elif isinstance(f, fields.function) and eval('fields.'+(f._type)) in type_dict: + t = eval('fields.'+(f._type)) + f_type = (type_dict[t], type_dict[t]) + elif isinstance(f, fields.function) and f._type == 'float': + f_type = ('float8', 'DOUBLE PRECISION') + elif isinstance(f, fields.function) and f._type == 'selection': + f_type = ('text', 'text') + else: + logger = netsvc.Logger() + logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f))) + f_type = None + return f_type + + +class orm_template(object): + _name = None + _columns = {} + _constraints = [] + _defaults = {} + _rec_name = 'name' + _parent_name = 'parent_id' + _date_name = 'date' + _order = 'id' + _sequence = None + _description = None + _inherits = {} + _table = None + + def _field_create(self, cr, context={}): + cr.execute("SELECT id FROM ir_model WHERE model='%s'" % self._name) + if not cr.rowcount: + # reference model in order to have a description of its fonctionnality in custom_report + cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',)) + id = cr.fetchone()[0] + cr.execute("INSERT INTO ir_model (id,model, name, info) VALUES (%s, %s, %s, %s)", (id, self._name, self._description, self.__doc__)) + if 'module' in context: + cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \ + ('model_'+self._table, context['module'], 'ir.model', id) + ) + cr.commit() + + cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,)) + cols = {} + for rec in cr.dictfetchall(): + cols[rec['name']] = rec + + cr.execute("SELECT id FROM ir_model WHERE model='%s'" % self._name) + model_id = cr.fetchone()[0] + + for (k, f) in self._columns.items(): + vals = { + 'model_id': model_id, + 'model': self._name, + 'name': k, + 'field_description': f.string.replace("'", " "), + 'ttype': f._type, + 'relate': (f.relate and 1) or 0, + 'relation': f._obj or 'NULL', + 'group_name': f.group_name or '', + 'view_load': (f.view_load and 1) or 0, + 'select_level': str(f.select or 0) + } + if k not in cols: + cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',)) + id = cr.fetchone()[0] + vals['id'] = id + cr.execute("""INSERT INTO ir_model_fields ( + id, model_id, model, name, field_description, ttype, + relate,relation,group_name,view_load,state,select_level + ) VALUES ( + %d,%s,%s,%s,%s,%s, %s,%s,%s,%s,%s, %s + )""", ( + id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'], + bool(vals['relate']), vals['relation'], vals['group_name'], bool(vals['view_load']), 'base', + vals['select_level'] + )) + if 'module' in context: + cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \ + (('field_'+self._table+'_'+k)[:64], context['module'], 'ir.model.fields', id) + ) + else: + for key, val in vals.items(): + if cols[k][key] != vals[key]: + print 'Different', cols[k][key], vals[key], k, key, self._name + cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name'])) + cr.commit() + cr.execute("""UPDATE ir_model_fields SET + model_id=%s, field_description=%s, ttype=%s, relate=%s, relation=%s, + group_name=%s, view_load=%s, select_level=%s + WHERE + model=%s AND name=%s""", ( + vals['model_id'], vals['field_description'], vals['ttype'], bool(vals['relate']), + vals['relation'], vals['group_name'], bool(vals['view_load']), + vals['select_level'], vals['model'], vals['name'] + )) + continue + cr.commit() + + def _auto_init(self, cr, context={}): + self._field_create(cr, context) + + def __init__(self, cr): + if not self._description: + self._description = self._name + if not self._table: + self._table = self._name.replace('.', '_') + + def browse(self, cr, uid, select, context=None, list_class=None, fields_process={}): + if not context: + context = {} + self._list_class = list_class or browse_record_list + cache = {} + # need to accepts ints and longs because ids coming from a method + # launched by button in the interface have a type long... + if isinstance(select, (int, long)): + return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) + elif isinstance(select, list): + return self._list_class([browse_record(cr, uid, id, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context) + else: + return browse_null() + + def __export_row(self, cr, uid, row, fields, context=None): + lines = [] + data = map(lambda x: '', range(len(fields))) + done = [] + for fpos in range(len(fields)): + f = fields[fpos] + if f: + r = row + i = 0 + while i < len(f): + r = r[f[i]] + if not r: + break + if isinstance(r, (browse_record_list, list)): + first = True + fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \ + or [], fields) + if fields2 in done: + break + done.append(fields2) + for row2 in r: + lines2 = self.__export_row(cr, uid, row2, fields2, + context) + if first: + for fpos2 in range(len(fields)): + if lines2 and lines2[0][fpos2]: + data[fpos2] = lines2[0][fpos2] + lines += lines2[1:] + first = False + else: + lines += lines2 + break + i += 1 + if i == len(f): + data[fpos] = str(r or '') + return [data] + lines + + def export_data(self, cr, uid, ids, fields, context=None): + if not context: + context = {} + fields = map(lambda x: x.split('/'), fields) + datas = [] + for row in self.browse(cr, uid, ids, context): + datas += self.__export_row(cr, uid, row, fields, context) + return datas + + def import_data(self, cr, uid, fields, datas, mode='init', + current_module=None, noupdate=False, context=None): + if not context: + context = {} + fields = map(lambda x: x.split('/'), fields) + logger = netsvc.Logger() + + def process_liness(self, datas, prefix, fields_def, position=0): + line = datas[position] + row = {} + translate = {} + todo = [] + warning = '' + data_id = False + # + # Import normal fields + # + for i in range(len(fields)): + if i >= len(line): + raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),)) + field = fields[i] + if field == ["id"]: + data_id = line[i] + continue + if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'): + res_id = False + if line[i]: + if fields_def[field[len(prefix)][:-3]]['type']=='many2many': + res_id = [] + for word in line[i].split(','): + if '.' in word: + module, xml_id = word.rsplit('.', 1) + else: + module, xml_id = current_module, word + ir_model_data_obj = self.pool.get('ir.model.data') + id = ir_model_data_obj._get_id(cr, uid, module, + xml_id) + res_id2 = ir_model_data_obj.read(cr, uid, [id], + ['res_id'])[0]['res_id'] + if res_id2: + res_id.append(res_id2) + if len(res_id): + res_id = [(6, 0, res_id)] + else: + if '.' in line[i]: + module, xml_id = line[i].rsplit('.', 1) + else: + module, xml_id = current_module, line[i] + ir_model_data_obj = self.pool.get('ir.model.data') + 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'] + row[field[0][:-3]] = res_id or False + continue + if (len(field) == len(prefix)+1) and \ + len(field[len(prefix)].split(':lang=')) == 2: + f, lang = field[len(prefix)].split(':lang=') + translate.setdefault(lang, {})[f]=line[i] or False + continue + if (len(field) == len(prefix)+1) and \ + (prefix == field[0:len(prefix)]): + if fields_def[field[len(prefix)]]['type'] == 'integer': + res = line[i] and int(line[i]) + elif fields_def[field[len(prefix)]]['type'] == 'float': + res = line[i] and float(line[i]) + elif fields_def[field[len(prefix)]]['type'] == 'selection': + res = False + if isinstance(fields_def[field[len(prefix)]]['selection'], + (tuple, list)): + sel = fields_def[field[len(prefix)]]['selection'] + else: + sel = fields_def[field[len(prefix)]]['selection'](self, + cr, uid, context) + for key, val in sel: + if str(key) == line[i]: + res = key + if line[i] and not res: + logger.notifyChannel("import", netsvc.LOG_WARNING, + "key '%s' not found in selection field '%s'" % \ + (line[i], field[len(prefix)])) + elif fields_def[field[len(prefix)]]['type']=='many2one': + res = False + if line[i]: + relation = fields_def[field[len(prefix)]]['relation'] + res2 = self.pool.get(relation).name_search(cr, uid, + line[i], [], operator='=') + res = (res2 and res2[0][0]) or False + if not res: + warning += ('Relation not found: ' + line[i] + \ + ' on ' + relation + ' !\n') + logger.notifyChannel("import", netsvc.LOG_WARNING, + 'Relation not found: ' + line[i] + \ + ' on ' + relation + ' !\n') + elif fields_def[field[len(prefix)]]['type']=='many2many': + res = [] + if line[i]: + relation = fields_def[field[len(prefix)]]['relation'] + for word in line[i].split(','): + res2 = self.pool.get(relation).name_search(cr, + uid, word, [], operator='=') + res3 = (res2 and res2[0][0]) or False + if not res3: + warning += ('Relation not found: ' + \ + line[i] + ' on '+relation + ' !\n') + logger.notifyChannel("import", + netsvc.LOG_WARNING, + 'Relation not found: ' + line[i] + \ + ' on '+relation + ' !\n') + else: + res.append(res3) + if len(res): + res = [(6, 0, res)] + else: + res = line[i] or False + row[field[len(prefix)]] = res + elif (prefix==field[0:len(prefix)]): + if field[0] not in todo: + todo.append(field[len(prefix)]) + # + # Import one2many fields + # + nbrmax = 1 + for field in todo: + newfd = self.pool.get(fields_def[field]['relation']).fields_get( + cr, uid, context=context) + res = process_liness(self, datas, prefix + [field], newfd, position) + (newrow, max2, w2, translate2, data_id2) = res + nbrmax = max(nbrmax, max2) + warning = warning + w2 + reduce(lambda x, y: x and y, newrow) + row[field] = (reduce(lambda x, y: x or y, newrow.values()) and \ + [(0, 0, newrow)]) or [] + i = max2 + while (position+i)\n' + '\n' + ' \n' + '') % (self._rec_name) + + return arch + + # + # if view_id, view_type is not required + # + def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False): + if not context: + context = {} + def _inherit_apply(src, inherit): + def _find(node, node2): + # Check if xpath query or normal inherit (with field matching) + if node2.nodeType == node2.ELEMENT_NODE and node2.localName == 'xpath': + res = xpath.Evaluate(node2.getAttribute('expr'), node) + return res and res[0] + else: + if node.nodeType == node.ELEMENT_NODE and node.localName == node2.localName: + res = True + for attr in node2.attributes.keys(): + if attr == 'position': + continue + if node.hasAttribute(attr): + if node.getAttribute(attr)==node2.getAttribute(attr): + continue + res = False + if res: + return node + for child in node.childNodes: + res = _find(child, node2) + if res: + return res + return None + + doc_src = dom.minidom.parseString(src) + doc_dest = dom.minidom.parseString(inherit) + toparse = doc_dest.childNodes + while len(toparse): + node2 = toparse.pop(0) + if not node2.nodeType == node2.ELEMENT_NODE: + continue + if node2.localName == 'data': + toparse += node2.childNodes + continue + node = _find(doc_src, node2) + if node: + pos = 'inside' + if node2.hasAttribute('position'): + pos = node2.getAttribute('position') + if pos == 'replace': + parent = node.parentNode + for child in node2.childNodes: + if child.nodeType == child.ELEMENT_NODE: + parent.insertBefore(child, node) + parent.removeChild(node) + else: + for child in node2.childNodes: + if child.nodeType == child.ELEMENT_NODE: + if pos == 'inside': + node.appendChild(child) + elif pos == 'after': + sib = node.nextSibling + if sib: + node.parentNode.insertBefore(child, sib) + else: + node.parentNode.appendChild(child) + elif pos=='before': + node.parentNode.insertBefore(child, node) + else: + raise AttributeError(_('Unknown position in inherited view %s !') % pos) + else: + attrs = ''.join([ + ' %s="%s"' % (attr, node2.getAttribute(attr)) + for attr in node2.attributes.keys() + if attr != 'position' + ]) + tag = "<%s%s>" % (node2.localName, attrs) + raise AttributeError(_("Couldn't find tag '%s' in parent view !") % tag) + return doc_src.toxml(encoding="utf-8").replace('\t', '') + + result = {'type': view_type, 'model': self._name} + + ok = True + model = True + sql_res = False + while ok: + if view_id: + where = (model and (" and model='%s'" % (self._name,))) or '' + cr.execute('SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE id=%d'+where, (view_id,)) + else: + cr.execute('SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE model=%s AND type=%s ORDER BY priority', (self._name, view_type)) + sql_res = cr.fetchone() + if not sql_res: + break + ok = sql_res[5] + view_id = ok or sql_res[3] + model = False + + # if a view was found + if sql_res: + result['type'] = sql_res[4] + result['view_id'] = sql_res[3] + result['arch'] = sql_res[0] + + def _inherit_apply_rec(result, inherit_id): + # get all views which inherit from (ie modify) this view + cr.execute('select arch,id from ir_ui_view where inherit_id=%d and model=%s order by priority', (inherit_id, self._name)) + sql_inherit = cr.fetchall() + for (inherit, id) in sql_inherit: + result = _inherit_apply(result, inherit) + result = _inherit_apply_rec(result, id) + return result + + result['arch'] = _inherit_apply_rec(result['arch'], sql_res[3]) + + result['name'] = sql_res[1] + result['field_parent'] = sql_res[2] or False + else: + # otherwise, build some kind of default view + if view_type == 'form': + res = self.fields_get(cr, user, context=context) + xml = '''''' \ + '''
''' % (self._description,) + for x in res: + if res[x]['type'] not in ('one2many', 'many2many'): + xml += '' % (x,) + if res[x]['type'] == 'text': + xml += "" + xml += "" + elif view_type == 'tree': + xml = '''''' \ + '''''' \ + % (self._description, self._rec_name) + elif view_type == 'calendar': + xml = self.__get_default_calendar_view() + else: + xml = '' + result['arch'] = xml + result['name'] = 'default' + result['field_parent'] = False + result['view_id'] = 0 + + doc = dom.minidom.parseString(result['arch'].encode('utf-8')) + xarch, xfields = self.__view_look_dom_arch(cr, user, doc, context=context) + result['arch'] = xarch + result['fields'] = xfields + if toolbar: + def clean(x): + x = x[2] + for key in ('report_sxw_content', 'report_rml_content', + 'report_sxw', 'report_rml', + 'report_sxw_content_data', 'report_rml_content_data'): + if key in x: + del x[key] + return x + ir_values_obj = self.pool.get('ir.values') + resprint = ir_values_obj.get(cr, user, 'action', + 'client_print_multi', [(self._name, False)], False, + context) + resaction = ir_values_obj.get(cr, user, 'action', + 'client_action_multi', [(self._name, False)], False, + context) + + resrelate = ir_values_obj.get(cr, user, 'action', + 'client_action_relate', [(self._name, False)], False, + context) + resprint = map(clean, resprint) + resaction = map(clean, resaction) + resaction = filter(lambda x: not x.get('multi', False), resaction) + resprint = filter(lambda x: not x.get('multi', False), resprint) + resrelate = map(lambda x: x[2], resrelate) + + for x in resprint+resaction+resrelate: + x['string'] = x['name'] + + result['toolbar'] = { + 'print': resprint, + 'action': resaction, + 'relate': resrelate + } + return result + + _view_look_dom_arch = __view_look_dom_arch + + def search_count(self, cr, user, args, context=None): + if not context: + context = {} + res = self.search(cr, user, args, context=context, count=True) + if isinstance(res, list): + return len(res) + return res + + def search(self, cr, user, args, offset=0, limit=None, order=None, + context=None, count=False): + raise _('The search method is not implemented on this object !') + + def name_get(self, cr, user, ids, context=None): + raise _('The name_get method is not implemented on this object !') + + def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=80): + raise _('The name_search method is not implemented on this object !') + + def copy(self, cr, uid, id, default=None, context=None): + raise _('The copy method is not implemented on this object !') + + +class orm_memory(orm_template): + _protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count'] + _inherit_fields = {} + _max_count = 200 + _max_hours = 1 + _check_time = 20 + + def __init__(self, cr): + super(orm_memory, self).__init__(cr) + self.datas = {} + self.next_id = 0 + self.check_id = 0 + cr.execute('delete from wkf_instance where res_type=%s', (self._name,)) def clear(self): - self._cache.clear() - self._items = [] - -def filter_dict(d, fields): - res = {} - for f in fields + ['id']: - if f in d: - res[f] = d[f] - return res - -class cacheable_osv(osv, Cacheable): - - _relevant = ['lang'] - - def __init__(self): - super(cacheable_osv, self).__init__() + self.check_id += 1 + if self.check_id % self._check_time: + return True + tounlink = [] + max = time.time() - self._max_hours * 60 * 60 + for id in self.datas: + if self.datas[id]['internal.date_access'] < max: + tounlink.append(id) + self.unlink(tounlink) + if len(self.datas)>self._max_count: + sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items()) + sorted.sort() + ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count]) + self.unlink(ids) + return True def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'): if not fields: - fields=[] - if not context: - context={} - fields = fields or self._columns.keys() - ctx = [context.get(x, False) for x in self._relevant] - result, tofetch = [], [] + fields = self._columns.keys() + result = [] for id in ids: - res = self.get(self._name, id, ctx) - if not res: - tofetch.append(id) - else: - result.append(filter_dict(res, fields)) - - # gen the list of "local" (ie not inherited) fields which are classic or many2one - nfields = filter(lambda x: x[1]._classic_write, self._columns.items()) - # gen the list of inherited fields - inherits = map(lambda x: (x[0], x[1][2]), self._inherit_fields.items()) - # complete the field list with the inherited fields which are classic or many2one - nfields += filter(lambda x: x[1]._classic_write, inherits) - nfields = [x[0] for x in nfields] - - res = super(cacheable_osv, self).read(cr, user, tofetch, nfields, context, load) - for r in res: - self.add((self._name, r['id'], ctx), r) - result.append(filter_dict(r, fields)) - - # Appel de fonction si necessaire - tofetch = [] - for f in fields: - if f not in nfields: - tofetch.append(f) - for f in tofetch: - fvals = self._columns[f].get(cr, self, ids, f, user, context=context) - for r in result: - r[f] = fvals[r['id']] - - # TODO: tri par self._order !! + r = {'id': id} + for f in fields: + r[f] = self.datas[id].get(f, False) + result.append(r) + self.datas[id]['internal.date_access'] = time.time() + fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields) + for f in fields_post: + res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=False) + for record in result: + record[f] = res2[record['id']] return result - def invalidate(self, key): - del self._cache[key[0]][key[1]] - - def write(self, cr, user, ids, values, context=None): - if not context: - context={} - for id in ids: - self.invalidate((self._name, id)) - return super(cacheable_osv, self).write(cr, user, ids, values, context) - - def unlink(self, cr, user, ids): + def write(self, cr, user, ids, vals, context=None): + vals2 = {} + upd_todo = [] + for field in vals: + if self._columns[field]._classic_write: + vals2[field] = vals[field] + else: + upd_todo.append(field) + for id_new in ids: + self.datas[id_new].update(vals2) + self.datas[id_new]['internal.date_access'] = time.time() + for field in upd_todo: + self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context) + self._validate(cr, user, [id_new], context) + wf_service = netsvc.LocalService("workflow") + wf_service.trg_write(user, self._name, id_new, cr) self.clear() - return super(cacheable_osv, self).unlink(cr, user, ids) + return id_new -#cacheable_osv = osv + def create(self, cr, user, vals, context=None): + self.next_id += 1 + id_new = self.next_id + default = [] + for f in self._columns.keys(): + if not f in vals: + default.append(f) + if len(default): + vals.update(self.default_get(cr, user, default, context)) + vals2 = {} + upd_todo = [] + for field in vals: + if self._columns[field]._classic_write: + vals2[field] = vals[field] + else: + upd_todo.append(field) + self.datas[id_new] = vals2 + self.datas[id_new]['internal.date_access'] = time.time() + for field in upd_todo: + self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context) + self._validate(cr, user, [id_new], context) + wf_service = netsvc.LocalService("workflow") + wf_service.trg_create(user, self._name, id_new, cr) + self.clear() + return id_new + + def default_get(self, cr, uid, fields_list, context=None): + if not context: + context = {} + value = {} + # get the default values for the inherited fields + for f in fields_list: + if f in self._defaults: + value[f] = self._defaults[f](self, cr, uid, context) + fld_def = ((f in self._columns) and self._columns[f]) \ + or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \ + or False + + # get the default values set by the user and override the default + # values defined in the object + ir_values_obj = self.pool.get('ir.values') + res = ir_values_obj.get(cr, uid, 'default', False, [self._name]) + for id, field, field_value in res: + if field in fields_list: + fld_def = (field in self._columns) + if fld_def._type in ('many2one', 'one2one'): + obj = self.pool.get(fld_def._obj) + if not obj.search(cr, uid, [('id', '=', field_value)]): + continue + if fld_def._type in ('many2many'): + obj = self.pool.get(fld_def._obj) + field_value2 = [] + for i in range(len(field_value)): + if not obj.search(cr, uid, [('id', '=', + field_value[i])]): + continue + field_value2.append(field_value[i]) + field_value = field_value2 + if fld_def._type in ('one2many'): + obj = self.pool.get(fld_def._obj) + field_value2 = [] + for i in range(len(field_value)): + field_value2.append({}) + for field2 in field_value[i]: + if obj._columns[field2]._type in ('many2one', 'one2one'): + obj2 = self.pool.get(obj._columns[field2]._obj) + if not obj2.search(cr, uid, + [('id', '=', field_value[i][field2])]): + continue + # TODO add test for many2many and one2many + field_value2[i][field2] = field_value[i][field2] + field_value = field_value2 + value[field] = field_value + return value + + def search(self, cr, user, args, offset=0, limit=None, order=None, + context=None, count=False): + return self.datas.keys() + + def unlink(self, cr, uid, ids, context=None): + for id in ids: + if id in self.datas: + del self.datas[id] + if len(ids): + cr.execute('delete from wkf_instance where res_type=%s and res_id in ('+','.join(map(str, ids))+')', (self._name, )) + return True + + def perm_read(self, cr, user, ids, context=None, details=True): + result = [] + for id in ids: + result.append({ + 'create_uid': (user, 'Root'), + 'create_date': time.strftime('%Y-%m-%d %H:%M:%S'), + 'write_uid': False, + 'write_date': False, + 'id': id + }) + return result + +class orm(orm_template): + + _sql_constraints = [] + + _log_access = True + _table = None + _protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count'] + def _auto_init(self, cr, context={}): + logger = netsvc.Logger() + create = False + self._field_create(cr, context=context) + if not hasattr(self, "_auto") or self._auto: + cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table) + if not cr.rowcount: + cr.execute("CREATE TABLE \"%s\" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITH OIDS" % self._table) + create = True + cr.commit() + if self._log_access: + logs = { + 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL', + 'create_date': 'TIMESTAMP', + 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL', + 'write_date': 'TIMESTAMP' + } + for k in logs: + cr.execute( + """ + SELECT c.relname + FROM pg_class c, pg_attribute a + WHERE c.relname='%s' AND a.attname='%s' AND c.oid=a.attrelid + """ % (self._table, k)) + if not cr.rowcount: + cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" %s" % + (self._table, k, logs[k])) + cr.commit() + + # iterate on the database columns to drop the NOT NULL constraints + # of fields which were required but have been removed + cr.execute( + "SELECT a.attname, a.attnotnull "\ + "FROM pg_class c, pg_attribute a "\ + "WHERE c.oid=a.attrelid AND c.relname='%s'" % self._table) + db_columns = cr.dictfetchall() + for column in db_columns: + if column['attname'] not in ('id', 'oid', 'tableoid', 'ctid', 'xmin', 'xmax', 'cmin', 'cmax'): + if column['attnotnull'] and column['attname'] not in self._columns: + cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" DROP NOT NULL" % (self._table, column['attname'])) + + # iterate on the "object columns" + for k in self._columns: + if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'): + continue + #raise _('Can not define a column %s. Reserved keyword !') % (k,) + f = self._columns[k] + + if isinstance(f, fields.one2many): + cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,)) + if cr.fetchone(): + cr.execute("SELECT count(*) as c FROM pg_class c,pg_attribute a WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid", (f._obj, f._fields_id)) + res = cr.fetchone()[0] + if not res: + cr.execute("ALTER TABLE \"%s\" ADD FOREIGN KEY (%s) REFERENCES \"%s\" ON DELETE SET NULL" % (self._obj, f._fields_id, f._table)) + 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) 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.commit() + else: + cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size FROM pg_class c,pg_attribute a,pg_type t WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid AND a.atttypid=t.oid", (self._table, k)) + res = cr.dictfetchall() + if not res: + if not isinstance(f, fields.function) or f.store: + + # add the missing field + cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" %s" % (self._table, k, get_pg_type(f)[1])) + + # initialize it + if not create and k in self._defaults: + default = self._defaults[k](self, cr, 1, {}) + if not default: + cr.execute("UPDATE \"%s\" SET \"%s\"=NULL" % (self._table, k)) + else: + cr.execute("UPDATE \"%s\" SET \"%s\"='%s'" % (self._table, k, default)) + if isinstance(f, fields.function): + cr.execute('select id from '+self._table) + ids_lst = map(lambda x: x[0], cr.fetchall()) + while ids_lst: + iids = ids_lst[:40] + ids_lst = ids_lst[40:] + res = f.get(cr, self, iids, k, 1, {}) + for r in res.items(): + cr.execute("UPDATE \"%s\" SET \"%s\"='%s' where id=%d"% (self._table, k, r[1], r[0])) + + # 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('.', '_') + # 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)) + if f.select: + cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (self._table, k, self._table, k)) + if f.required: + cr.commit() + try: + cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" SET NOT NULL" % (self._table, k)) + except: + logger.notifyChannel('init', netsvc.LOG_WARNING, 'WARNING: unable to set column %s of table %s not null !\nTry to re-run: tinyerp-server.py --update=module\nIf it doesn\'t work, update records and execute manually:\nALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % (k, self._table, self._table, k)) + cr.commit() + elif len(res)==1: + f_pg_def = res[0] + f_pg_type = f_pg_def['typname'] + f_pg_size = f_pg_def['size'] + f_pg_notnull = f_pg_def['attnotnull'] + if isinstance(f, fields.function) and not f.store: + logger.notifyChannel('init', netsvc.LOG_WARNING, 'column %s (%s) in table %s was converted to a function !\nYou should remove this column from your database.' % (k, f.string, self._table)) + f_obj_type = None + else: + f_obj_type = get_pg_type(f) and get_pg_type(f)[0] + + if f_obj_type: + if f_pg_type != f_obj_type: + logger.notifyChannel('init', netsvc.LOG_WARNING, "column '%s' in table '%s' has changed type (DB = %s, def = %s) !" % (k, self._table, f_pg_type, f._type)) + if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size != f.size: + # columns with the name 'type' cannot be changed for an unknown reason?! + if k != 'type': + if f_pg_size > f.size: + logger.notifyChannel('init', netsvc.LOG_WARNING, "column '%s' in table '%s' has changed size (DB = %d, def = %d), DB size will be kept !" % (k, self._table, f_pg_size, f.size)) + # If actual DB size is < than new + # We update varchar size, otherwise, we keep DB size + # to avoid truncated string... + if f_pg_size < f.size: + cr.execute("ALTER TABLE \"%s\" RENAME COLUMN \"%s\" TO temp_change_size" % (self._table, k)) + cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" VARCHAR(%d)" % (self._table, k, f.size)) + cr.execute("UPDATE \"%s\" SET \"%s\"=temp_change_size::VARCHAR(%d)" % (self._table, k, f.size)) + cr.execute("ALTER TABLE \"%s\" DROP COLUMN temp_change_size" % (self._table,)) + cr.commit() + # if the field is required and hasn't got a NOT NULL constraint + if f.required and f_pg_notnull == 0: + # set the field to the default value if any + if k in self._defaults: + default = self._defaults[k](self, cr, 1, {}) + if not (default is False): + cr.execute("UPDATE \"%s\" SET \"%s\"='%s' WHERE %s is NULL" % (self._table, k, default, k)) + cr.commit() + # add the NOT NULL constraint + try: + cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" SET NOT NULL" % (self._table, k)) + cr.commit() + except: + logger.notifyChannel('init', netsvc.LOG_WARNING, 'unable to set a NOT NULL constraint on column %s of the %s table !\nIf you want to have it, you should update the records and execute manually:\nALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % (k, self._table, self._table, k)) + cr.commit() + elif not f.required and f_pg_notnull == 1: + cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" DROP NOT NULL" % (self._table, k)) + cr.commit() + cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = '%s_%s_index' and tablename = '%s'" % (self._table, k, self._table)) + res = cr.dictfetchall() + if not res and f.select: + cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (self._table, k, self._table, k)) + cr.commit() + if res and not f.select: + cr.execute("DROP INDEX \"%s_%s_index\"" % (self._table, k)) + cr.commit() + if isinstance(f, fields.many2one): + ref = self.pool.get(f._obj)._table + if ref != 'ir_actions': + cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, ' \ + 'pg_attribute as att1, pg_attribute as att2 ' \ + 'WHERE con.conrelid = cl1.oid ' \ + 'AND cl1.relname = %s ' \ + 'AND con.confrelid = cl2.oid ' \ + 'AND cl2.relname = %s ' \ + 'AND array_lower(con.conkey, 1) = 1 ' \ + 'AND con.conkey[1] = att1.attnum ' \ + 'AND att1.attrelid = cl1.oid ' \ + 'AND att1.attname = %s ' \ + 'AND array_lower(con.confkey, 1) = 1 ' \ + 'AND con.confkey[1] = att2.attnum ' \ + 'AND att2.attrelid = cl2.oid ' \ + 'AND att2.attname = %s ' \ + 'AND con.contype = \'f\'', (self._table, ref, k, 'id')) + res = cr.dictfetchall() + if res: + confdeltype = { + 'RESTRICT': 'r', + 'NO ACTION': 'a', + 'CASCADE': 'c', + 'SET NULL': 'n', + 'SET DEFAULT': 'd', + } + if res[0]['confdeltype'] != confdeltype.get(f.ondelete.upper(), 'a'): + cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res[0]['conname'] + '"') + cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete) + cr.commit() + else: + print "ERROR" + else: + cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table) + create = not bool(cr.fetchone()) + + for (key, con, _) in self._sql_constraints: + cr.execute("SELECT conname FROM pg_constraint where conname='%s_%s'" % (self._table, key)) + if not cr.dictfetchall(): + try: + cr.execute('alter table \"%s\" add constraint \"%s_%s\" %s' % (self._table, self._table, key, con,)) + cr.commit() + except: + logger.notifyChannel('init', netsvc.LOG_WARNING, 'unable to add \'%s\' constraint on table %s !\n If you want to have it, you should update the records and execute manually:\nALTER table %s ADD CONSTRAINT %s_%s %s' % (con, self._table, self._table, self._table, key, con,)) + + if create: + if hasattr(self, "_sql"): + for line in self._sql.split(';'): + line2 = line.replace('\n', '').strip() + if line2: + cr.execute(line2) + cr.commit() + + def __init__(self, cr): + super(orm, self).__init__(cr) + f = filter(lambda a: isinstance(self._columns[a], fields.function) and self._columns[a].store, self._columns) + if f: + list_store = [] + tuple_store = () + tuple_fn = () + for store_field in f: + if not self._columns[store_field].store == True: + dict_store = self._columns[store_field].store + key = dict_store.keys() + list_data = [] + for i in key: + tuple_store = self._name, store_field, self._columns[store_field]._fnct.__name__, tuple(dict_store[i][0]), dict_store[i][1], i + list_data.append(tuple_store) + #tuple_store=self._name,store_field,self._columns[store_field]._fnct.__name__,tuple(dict_store[key[0]][0]),dict_store[key[0]][1] + for l in list_data: + list_store = [] + if l[5] in self.pool._store_function.keys(): + self.pool._store_function[l[5]].append(l) + temp_list = list(set(self.pool._store_function[l[5]])) + self.pool._store_function[l[5]] = temp_list + else: + list_store.append(l) + self.pool._store_function[l[5]] = list_store + + for (key, _, msg) in self._sql_constraints: + self.pool._sql_error[self._table+'_'+key] = msg + + # Load manual fields + + cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields')) + if cr.fetchone(): + cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual')) + for field in cr.dictfetchall(): + if field['name'] in self._columns: + continue + attrs = { + 'string': field['field_description'], + 'required': bool(field['required']), + 'readonly': bool(field['readonly']), + 'domain': field['domain'] or None, + 'size': field['size'], + 'ondelete': field['on_delete'], + 'translate': (field['translate']), + #'select': int(field['select_level']) + } + #if field['relation']: + # attrs['relation'] = field['relation'] + if field['ttype'] == 'selection': + self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs) + elif field['ttype'] == 'many2one': + self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs) + else: + self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs) + + self._inherits_reload() + if not self._sequence: + self._sequence = self._table+'_id_seq' + for k in self._defaults: + assert (k in self._columns) or (k in self._inherit_fields), 'Default function defined in %s but field %s does not exist !' % (self._name, k,) + for f in self._columns: + self._columns[f].restart() + + def default_get(self, cr, uid, fields_list, context=None): + if not context: + context = {} + value = {} + # get the default values for the inherited fields + for t in self._inherits.keys(): + value.update(self.pool.get(t).default_get(cr, uid, fields_list, + context)) + + # get the default values defined in the object + for f in fields_list: + if f in self._defaults: + value[f] = self._defaults[f](self, cr, uid, context) + fld_def = ((f in self._columns) and self._columns[f]) \ + or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \ + or False + if isinstance(fld_def, fields.property): + property_obj = self.pool.get('ir.property') + definition_id = fld_def._field_get(cr, uid, self._name, f) + nid = property_obj.search(cr, uid, [('fields_id', '=', + definition_id), ('res_id', '=', False)]) + if nid: + prop_value = property_obj.browse(cr, uid, nid[0], + context=context).value + value[f] = (prop_value and int(prop_value.split(',')[1])) \ + or False + + # get the default values set by the user and override the default + # values defined in the object + ir_values_obj = self.pool.get('ir.values') + res = ir_values_obj.get(cr, uid, 'default', False, [self._name]) + for id, field, field_value in res: + if field in fields_list: + fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2] + if fld_def._type in ('many2one', 'one2one'): + obj = self.pool.get(fld_def._obj) + if not obj.search(cr, uid, [('id', '=', field_value)]): + continue + if fld_def._type in ('many2many'): + obj = self.pool.get(fld_def._obj) + field_value2 = [] + for i in range(len(field_value)): + if not obj.search(cr, uid, [('id', '=', + field_value[i])]): + continue + field_value2.append(field_value[i]) + field_value = field_value2 + if fld_def._type in ('one2many'): + obj = self.pool.get(fld_def._obj) + field_value2 = [] + for i in range(len(field_value)): + field_value2.append({}) + for field2 in field_value[i]: + if obj._columns[field2]._type in ('many2one', 'one2one'): + obj2 = self.pool.get(obj._columns[field2]._obj) + if not obj2.search(cr, uid, + [('id', '=', field_value[i][field2])]): + continue + # TODO add test for many2many and one2many + field_value2[i][field2] = field_value[i][field2] + field_value = field_value2 + value[field] = field_value + return value -#class FakePool(object): -# def __init__(self, module): -# self.preferred_module = module + # + # Update objects that uses this one to update their _inherits fields + # + def _inherits_reload_src(self): + for obj in self.pool.obj_pool.values(): + if self._name in obj._inherits: + obj._inherits_reload() -# def get(self, name): -# localpool = module_objects_dict.get(self.preferred_module, {'dict': {}})['dict'] -# if name in localpool: -# obj = localpool[name] -# else: -# obj = pooler.get_pool(cr.dbname).get(name) -# return obj + def _inherits_reload(self): + res = {} + for table in self._inherits: + res.update(self.pool.get(table)._inherit_fields) + for col in self.pool.get(table)._columns.keys(): + res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col]) + for col in self.pool.get(table)._inherit_fields.keys(): + res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2]) + self._inherit_fields = res + self._inherits_reload_src() -# fake_pool = self -# class fake_class(obj.__class__): -# def __init__(self): -# super(fake_class, self).__init__() -# self.pool = fake_pool + def fields_get(self, cr, user, fields=None, context=None): + read_access = self.pool.get('ir.model.access').check(cr, user, self._name, 'write', raise_exception=False) + return super(orm, self).fields_get(cr, user, fields, context, read_access) -# return fake_class() + def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'): + if not context: + context = {} + self.pool.get('ir.model.access').check(cr, user, self._name, 'read') + if not fields: + fields = self._columns.keys() + self._inherit_fields.keys() + select = ids + if isinstance(ids, (int, long)): + select = [ids] + result = self._read_flat(cr, user, select, fields, context, load) + for r in result: + for key, v in r.items(): + if v == None: + r[key] = False + if isinstance(ids, (int, long)): + return result[0] + return result + + def _read_flat(self, cr, user, ids, fields, context=None, load='_classic_read'): + if not context: + context = {} + if not ids: + return [] + + if fields == None: + fields = self._columns.keys() + + # construct a clause for the rules : + d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name) + + # all inherited fields + all non inherited fields for which the attribute whose name is in load is True + fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x], '_classic_write'), fields) + self._inherits.values() + + res = [] + if len(fields_pre): + fields_pre2 = map(lambda x: (x in ('create_date', 'write_date')) and ('date_trunc(\'second\', '+x+') as '+x) or '"'+x+'"', fields_pre) + 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 id IN (%s) AND %s ORDER BY %s' % \ + (','.join(fields_pre2 + ['id']), self._table, + ','.join([str(x) for x in sub_ids]), d1, + self._order), d2) + if not cr.rowcount == len({}.fromkeys(sub_ids)): + raise except_orm(_('AccessError'), + _('You try to bypass an access rule (Document type: %s).') % self._description) + else: + cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) ORDER BY %s' % \ + (','.join(fields_pre2 + ['id']), self._table, + ','.join([str(x) for x in sub_ids]), + self._order)) + res.extend(cr.dictfetchall()) + else: + res = map(lambda x: {'id': x}, ids) + + for f in fields_pre: + if self._columns[f].translate: + ids = map(lambda x: x['id'], res) + res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids) + for r in res: + r[f] = res_trans.get(r['id'], False) or r[f] + + for table in self._inherits: + col = self._inherits[table] + cols = intersect(self._inherit_fields.keys(), fields) + if not cols: + continue + res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load) + + res3 = {} + for r in res2: + res3[r['id']] = r + del r['id'] + + for record in res: + record.update(res3[record[col]]) + if col not in fields: + del record[col] + + # all fields which need to be post-processed by a simple function (symbol_get) + fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields) + if fields_post: + # maybe it would be faster to iterate on the fields then on res, so that we wouldn't need + # to get the _symbol_get in each occurence + for r in res: + for f in fields_post: + r[f] = self.columns[f]._symbol_get(r[f]) + ids = map(lambda x: x['id'], res) + + # all non inherited fields for which the attribute whose name is in load is False + fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields) + for f in fields_post: + # get the value of that field for all records/ids + res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res) + for record in res: + record[f] = res2[record['id']] + + readonly = None + for vals in res: + for field in vals.copy(): + fobj = None + if field in self._columns: + fobj = self._columns[field] + + if not fobj: + continue + groups = fobj.read + if groups: + edit = False + for group in groups: + module = group.split(".")[0] + grp = group.split(".")[1] + cr.execute("select count(*) from res_groups_users_rel where gid in (select res_id from ir_model_data where name='%s' and module='%s' and model='%s') and uid=%s" % \ + (grp, module, 'res.groups', user)) + readonly = cr.fetchall() + if readonly[0][0] >= 1: + edit = True + break + elif readonly[0][0] == 0: + edit = False + else: + edit = False + + if not edit: + if type(vals[field]) == type([]): + vals[field] = [] + elif type(vals[field]) == type(0.0): + vals[field] = 0 + elif type(vals[field]) == type(''): + vals[field] = '=No Permission=' + else: + vals[field] = False + return res + + def perm_read(self, cr, user, ids, context=None, details=True): + if not context: + context = {} + if not ids: + return [] + fields = '' + if self._log_access: + fields = ', u.create_uid, u.create_date, u.write_uid, u.write_date' + if isinstance(ids, (int, long)): + ids_str = str(ids) + else: + ids_str = ','.join([str(i) for i in ids]) + cr.execute('select u.id'+fields+' from "'+self._table+'" u where u.id in ('+ids_str+')') + res = cr.dictfetchall() + for r in res: + for key in r: + r[key] = r[key] or False + if key in ('write_uid', 'create_uid', 'uid') and details: + if r[key]: + r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0] + if isinstance(ids, (int, long)): + return res[ids] + return res + + def unlink(self, cr, uid, ids, context=None): + if not context: + context = {} + if not ids: + return True + if isinstance(ids, (int, long)): + ids = [ids] + + fn_list = [] + if self._name in self.pool._store_function.keys(): + list_store = self.pool._store_function[self._name] + fn_data = () + id_change = [] + for tuple_fn in list_store: + for id in ids: + id_change.append(self._store_get_ids(cr, uid, id, tuple_fn, context)[0]) + fn_data = id_change, tuple_fn + fn_list.append(fn_data) + + delta = context.get('read_delta', False) + if delta and self._log_access: + for i in range(0, len(ids), cr.IN_MAX): + sub_ids = ids[i:i+cr.IN_MAX] + cr.execute("select (now() - min(write_date)) <= '%s'::interval " \ + "from \"%s\" where id in (%s)" % + (delta, self._table, ",".join(map(str, sub_ids)))) + res = cr.fetchone() + if res and res[0]: + raise except_orm(_('ConcurrencyException'), + _('This record was modified in the meanwhile')) + + self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink') + + wf_service = netsvc.LocalService("workflow") + for id in ids: + wf_service.trg_delete(uid, self._name, id, cr) + + #cr.execute('select * from '+self._table+' where id in ('+str_d+')', ids) + #res = cr.dictfetchall() + #for key in self._inherits: + # ids2 = [x[self._inherits[key]] for x in res] + # self.pool.get(key).unlink(cr, uid, ids2) + + d1, d2 = self.pool.get('ir.rule').domain_get(cr, uid, self._name) + if d1: + d1 = ' AND '+d1 + + for i in range(0, len(ids), cr.IN_MAX): + sub_ids = ids[i:i+cr.IN_MAX] + ','.join(('%d',)*len(sub_ids)) + if d1: + cr.execute('SELECT id FROM "'+self._table+'" ' \ + 'WHERE id IN ('+str_d+')'+d1, sub_ids+d2) + if not cr.rowcount == len({}.fromkeys(ids)): + raise except_orm(_('AccessError'), + _('You try to bypass an access rule (Document type: %s).') % \ + self._description) + + if d1: + cr.execute('delete from "'+self._table+'" ' \ + 'where id in ('+str_d+')'+d1, sub_ids+d2) + else: + cr.execute('delete from "'+self._table+'" ' \ + 'where id in ('+str_d+')', sub_ids) + if fn_list: + for ids, tuple_fn in fn_list: + self._store_set_values(cr, uid, ids, tuple_fn, id_change, context) + + return True + + # + # TODO: Validate + # + def write(self, cr, user, ids, vals, context=None): + readonly = None + for field in vals.copy(): + fobj = None + if field in self._columns: + fobj = self._columns[field] + else: + fobj = self._inherit_fields[field][2] + if not fobj: + continue + groups = fobj.write + + if groups: + edit = False + for group in groups: + module = group.split(".")[0] + grp = group.split(".")[1] + cr.execute("select count(*) from res_groups_users_rel where gid in (select res_id from ir_model_data where name='%s' and module='%s' and model='%s') and uid=%s" % \ + (grp, module, 'res.groups', user)) + readonly = cr.fetchall() + if readonly[0][0] >= 1: + edit = True + break + elif readonly[0][0] == 0: + edit = False + else: + edit = False + + if not edit: + vals.pop(field) + + if not context: + context = {} + if not ids: + return True + if isinstance(ids, (int, long)): + ids = [ids] + delta = context.get('read_delta', False) + if delta and self._log_access: + for i in range(0, len(ids), cr.IN_MAX): + sub_ids = ids[i:i+cr.IN_MAX] + cr.execute("select (now() - min(write_date)) <= '%s'::interval " \ + "from %s where id in (%s)" % + (delta, self._table, ",".join(map(str, sub_ids)))) + res = cr.fetchone() + if res and res[0]: + for field in vals: + if field in self._columns and self._columns[field]._classic_write: + raise except_orm(_('ConcurrencyException'), + _('This record was modified in the meanwhile')) + + self.pool.get('ir.model.access').check(cr, user, self._name, 'write') + + #for v in self._inherits.values(): + # assert v not in vals, (v, vals) + upd0 = [] + upd1 = [] + upd_todo = [] + updend = [] + direct = [] + totranslate = context.get('lang', False) and (context['lang'] != 'en_US') + for field in vals: + if field in self._columns: + if self._columns[field]._classic_write: + if (not totranslate) or not self._columns[field].translate: + upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0]) + upd1.append(self._columns[field]._symbol_set[1](vals[field])) + direct.append(field) + else: + upd_todo.append(field) + else: + updend.append(field) + if field in self._columns \ + and hasattr(self._columns[field], 'selection') \ + and vals[field]: + if self._columns[field]._type == 'reference': + val = vals[field].split(',')[0] + else: + val = vals[field] + if isinstance(self._columns[field].selection, (tuple, list)): + if val not in dict(self._columns[field].selection): + raise except_orm(_('ValidateError'), + _('The value "%s" for the field "%s" is not in the selection') \ + % (vals[field], field)) + else: + if val not in dict(self._columns[field].selection( + self, cr, user, context=context)): + raise except_orm(_('ValidateError'), + _('The value "%s" for the field "%s" is not in the selection') \ + % (vals[field], field)) + + if self._log_access: + upd0.append('write_uid=%d') + upd0.append('write_date=now()') + upd1.append(user) + + if len(upd0): + + d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name) + if d1: + d1 = ' and '+d1 + + for i in range(0, len(ids), cr.IN_MAX): + sub_ids = ids[i:i+cr.IN_MAX] + ids_str = ','.join(map(str, sub_ids)) + if d1: + cr.execute('SELECT id FROM "'+self._table+'" ' \ + 'WHERE id IN ('+ids_str+')'+d1, d2) + if not cr.rowcount == len({}.fromkeys(sub_ids)): + raise except_orm(_('AccessError'), + _('You try to bypass an access rule (Document type: %s).') % \ + self._description) + else: + cr.execute('SELECT id FROM "'+self._table+'" WHERE id IN ('+ids_str+')') + if not cr.rowcount == len({}.fromkeys(sub_ids)): + raise except_orm(_('AccessError'), + _('You try to write on an record that doesn\'t exist ' \ + '(Document type: %s).') % self._description) + if d1: + cr.execute('update "'+self._table+'" set ' + ','.join(upd0) + ' ' \ + 'where id in ('+ids_str+')'+d1, upd1+ d2) + else: + cr.execute('update "'+self._table+'" set ' + ','.join(upd0) + ' ' \ + 'where id in ('+ids_str+')', upd1) + + if totranslate: + for f in direct: + if self._columns[f].translate: + self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f]) + + # call the 'set' method of fields which are not classic_write + upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority) + for field in upd_todo: + for id in ids: + self._columns[field].set(cr, self, id, field, vals[field], user, context=context) + + for table in self._inherits: + col = self._inherits[table] + nids = [] + for i in range(0, len(ids), cr.IN_MAX): + sub_ids = ids[i:i+cr.IN_MAX] + ids_str = ','.join(map(str, sub_ids)) + cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \ + 'where id in ('+ids_str+')', upd1) + nids.extend([x[0] for x in cr.fetchall()]) + + v = {} + for val in updend: + if self._inherit_fields[val][0] == table: + v[val] = vals[val] + self.pool.get(table).write(cr, user, nids, v, context) + + self._validate(cr, user, ids, context) + + if 'read_delta' in context: + del context['read_delta'] + + wf_service = netsvc.LocalService("workflow") + for id in ids: + wf_service.trg_write(user, self._name, id, cr) + self._update_function_stored(cr, user, ids, context=context) + + if self._name in self.pool._store_function.keys(): + list_store = self.pool._store_function[self._name] + for tuple_fn in list_store: + flag = False + if not tuple_fn[3]: + flag = True + for field in tuple_fn[3]: + if field in vals.keys(): + flag = True + break + if flag: + id_change = self._store_get_ids(cr, user, ids[0], tuple_fn, context) + self._store_set_values(cr, user, ids[0], tuple_fn, id_change, context) + + return True + + # + # TODO: Should set perm to user.xxx + # + def create(self, cr, user, vals, context=None): + """ create(cr, user, vals, context) -> int + cr = database cursor + user = user id + vals = dictionary of the form {'field_name':field_value, ...} + """ + if not context: + context = {} + self.pool.get('ir.model.access').check(cr, user, self._name, 'create') + + default = [] + + avoid_table = [] + for (t, c) in self._inherits.items(): + if c in vals: + avoid_table.append(t) + for f in self._columns.keys(): # + self._inherit_fields.keys(): + if not f in vals: + default.append(f) + for f in self._inherit_fields.keys(): + if (not f in vals) and (not self._inherit_fields[f][0] in avoid_table): + default.append(f) + + if len(default): + vals.update(self.default_get(cr, user, default, context)) + + tocreate = {} + for v in self._inherits: + if self._inherits[v] not in vals: + tocreate[v] = {} + + (upd0, upd1, upd2) = ('', '', []) + upd_todo = [] + + for v in vals.keys(): + if v in self._inherit_fields: + (table, col, col_detail) = self._inherit_fields[v] + tocreate[table][v] = vals[v] + del vals[v] + + cr.execute("SELECT nextval('"+self._sequence+"')") + id_new = cr.fetchone()[0] + for table in tocreate: + id = self.pool.get(table).create(cr, user, tocreate[table]) + upd0 += ','+self._inherits[table] + upd1 += ',%d' + upd2.append(id) + + for field in vals: + if self._columns[field]._classic_write: + upd0 = upd0 + ',"' + field+'"' + upd1 = upd1 + ',' + self._columns[field]._symbol_set[0] + upd2.append(self._columns[field]._symbol_set[1](vals[field])) + else: + upd_todo.append(field) + if field in self._columns \ + and hasattr(self._columns[field], 'selection') \ + and vals[field]: + if self._columns[field]._type == 'reference': + val = vals[field].split(',')[0] + else: + val = vals[field] + if isinstance(self._columns[field].selection, (tuple, list)): + if val not in dict(self._columns[field].selection): + raise except_orm(_('ValidateError'), + _('The value "%s" for the field "%s" is not in the selection') \ + % (vals[field], field)) + else: + if val not in dict(self._columns[field].selection( + self, cr, user, context=context)): + raise except_orm(_('ValidateError'), + _('The value "%s" for the field "%s" is not in the selection') \ + % (vals[field], field)) + if self._log_access: + upd0 += ',create_uid,create_date' + upd1 += ',%d,now()' + upd2.append(user) + cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2)) + upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority) + for field in upd_todo: + self._columns[field].set(cr, self, id_new, field, vals[field], user, context) + + self._validate(cr, user, [id_new], context) + + wf_service = netsvc.LocalService("workflow") + wf_service.trg_create(user, self._name, id_new, cr) + self._update_function_stored(cr, user, [id_new], context=context) + if self._name in self.pool._store_function.keys(): + list_store = self.pool._store_function[self._name] + for tuple_fn in list_store: + id_change = self._store_get_ids(cr, user, id_new, tuple_fn, context) + self._store_set_values(cr, user, id_new, tuple_fn, id_change, context) + + return id_new + + def _store_get_ids(self, cr, uid, ids, tuple_fn, context): + parent_id = getattr(self.pool.get(tuple_fn[0]), tuple_fn[4].func_name)(cr, uid, [ids]) + return parent_id + + def _store_set_values(self, cr, uid, ids, tuple_fn, parent_id, context): + name = tuple_fn[1] + table = tuple_fn[0] + args = {} + vals_tot = getattr(self.pool.get(table), tuple_fn[2])(cr, uid, parent_id, name, args, context) + write_dict = {} + for id in vals_tot.keys(): + write_dict[name] = vals_tot[id] + self.pool.get(table).write(cr, uid, [id], write_dict) + return True + + def _update_function_stored(self, cr, user, ids, context=None): + if not context: + context = {} + f = filter(lambda a: isinstance(self._columns[a], fields.function) \ + and self._columns[a].store, self._columns) + if f: + result = self.read(cr, user, ids, fields=f, context=context) + for res in result: + upd0 = [] + upd1 = [] + for field in res: + if field not in f: + continue + value = res[field] + if self._columns[field]._type in ('many2one', 'one2one'): + value = res[field][0] + upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0]) + upd1.append(self._columns[field]._symbol_set[1](value)) + upd1.append(res['id']) + cr.execute('update "' + self._table + '" set ' + \ + ','.join(upd0) + ' where id = %d', upd1) + return True + + # + # TODO: Validate + # + def perm_write(self, cr, user, ids, fields, context=None): + raise _('This method does not exist anymore') + + # TODO: ameliorer avec NULL + def _where_calc(self, cr, user, args, active_test=True, context=None): + if not context: + context = {} + args = args[:] + # if the object has a field named 'active', filter out all inactive + # records unless they were explicitely asked for + if 'active' in self._columns and (active_test and context.get('active_test', True)): + args = [('&', ('active', '=', 1), tuple(args))] + + if args: + import expression + e = expression.expression(args) + e.parse(cr, user, self, context) + tables = e.get_tables() + qu1, qu2 = e.to_sql() + qu1 = qu1 and [qu1] or [] + else: + qu1, qu2, tables = [], [], ['"%s"' % self._table] + + return (qu1, qu2, tables) + + def _check_qorder(self, word): + if not regex_order.match(word): + raise except_orm(_('AccessError'), _('Bad query.')) + return True + + def search(self, cr, user, args, offset=0, limit=None, order=None, + context=None, count=False): + if not context: + context = {} + # compute the where, order by, limit and offset clauses + (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context) + + if len(qu1): + qu1 = ' where ' + ' and '.join(qu1) + else: + qu1 = '' + + if order: + self._check_qorder(order) + order_by = order or self._order + + limit_str = limit and ' limit %d' % limit or '' + offset_str = offset and ' offset %d' % offset or '' + + + # construct a clause for the rules : + d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name) + if d1: + qu1 = qu1 and qu1+' and '+d1 or ' where '+d1 + qu2 += d2 + + if count: + cr.execute('select count(%s.id) from ' % self._table + + ','.join(tables) +qu1 + limit_str + offset_str, qu2) + res = cr.fetchall() + return res[0][0] + # execute the "main" query to fetch the ids we were searching for + cr.execute('select %s.id from ' % self._table + ','.join(tables) +qu1+' order by '+order_by+limit_str+offset_str, qu2) + res = cr.fetchall() + return [x[0] for x in res] + + # returns the different values ever entered for one field + # this is used, for example, in the client when the user hits enter on + # a char field + def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None): + if not args: + args = [] + if field in self._inherit_fields: + return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit) + else: + return self._columns[field].search(cr, self, args, field, value, offset, limit, uid) + + def name_get(self, cr, user, ids, context=None): + if not context: + context = {} + if not ids: + return [] + if isinstance(ids, (int, long)): + ids = [ids] + return [(r['id'], str(r[self._rec_name])) for r in self.read(cr, user, ids, + [self._rec_name], context, load='_classic_write')] + + def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=80): + if not args: + args = [] + if not context: + context = {} + args = args[:] + if name: + args += [(self._rec_name, operator, name)] + ids = self.search(cr, user, args, limit=limit, context=context) + res = self.name_get(cr, user, ids, context) + return res + + def copy(self, cr, uid, id, default=None, context=None): + if not context: + context = {} + if not default: + default = {} + if 'state' not in default: + if 'state' in self._defaults: + default['state'] = self._defaults['state'](self, cr, uid, context) + data = self.read(cr, uid, [id], context=context)[0] + fields = self.fields_get(cr, uid) + for f in fields: + ftype = fields[f]['type'] + + if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'): + del data[f] + + if f in default: + data[f] = default[f] + elif ftype == 'function': + del data[f] + elif ftype == 'many2one': + try: + data[f] = data[f] and data[f][0] + except: + pass + elif ftype in ('one2many', 'one2one'): + res = [] + rel = self.pool.get(fields[f]['relation']) + for rel_id in data[f]: + # the lines are first duplicated using the wrong (old) + # parent but then are reassigned to the correct one thanks + # to the (4, ...) + res.append((4, rel.copy(cr, uid, rel_id, context=context))) + data[f] = res + elif ftype == 'many2many': + data[f] = [(6, 0, data[f])] + del data['id'] + for v in self._inherits: + del data[self._inherits[v]] + return self.create(cr, uid, data) + + def read_string(self, cr, uid, id, langs, fields=None, context=None): + if not context: + context = {} + res = {} + res2 = {} + self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read') + if not fields: + fields = self._columns.keys() + self._inherit_fields.keys() + for lang in langs: + res[lang] = {'code': lang} + for f in fields: + if f in self._columns: + res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang) + if res_trans: + res[lang][f] = res_trans + else: + res[lang][f] = self._columns[f].string + for table in self._inherits: + cols = intersect(self._inherit_fields.keys(), fields) + res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context) + for lang in res2: + if lang in res: + res[lang] = {'code': lang} + for f in res2[lang]: + res[lang][f] = res2[lang][f] + return res + + def write_string(self, cr, uid, id, langs, vals, context=None): + if not context: + context = {} + self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write') + for lang in langs: + for field in vals: + if field in self._columns: + self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field]) + for table in self._inherits: + cols = intersect(self._inherit_fields.keys(), vals) + if cols: + self.pool.get(table).write_string(cr, uid, id, langs, vals, context) + return True + + def check_recursion(self, cr, uid, ids, parent=None): + if not parent: + parent = self._parent_name + ids_parent = ids[:] + while len(ids_parent): + ids_parent2 = [] + for i in range(0, len(ids), cr.IN_MAX): + sub_ids_parent = ids_parent[i:i+cr.IN_MAX] + cr.execute('SELECT distinct "'+parent+'"'+ + ' FROM "'+self._table+'" ' \ + 'WHERE id in ('+','.join(map(str, sub_ids_parent))+')') + ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall()))) + ids_parent = ids_parent2 + for i in ids_parent: + if i in ids: + return False + return True # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/pooler.py b/bin/pooler.py index 4f47a1f158b..e5c5b82be98 100644 --- a/bin/pooler.py +++ b/bin/pooler.py @@ -36,6 +36,7 @@ import netsvc db_dic = {} pool_dic = {} + def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False): if not status: status={} @@ -46,7 +47,7 @@ def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False) logger.notifyChannel('pooler', netsvc.LOG_INFO, 'Connecting to %s' % (db_name)) db = sql_db.db_connect(db_name) db_dic[db_name] = db - + if db_name in pool_dic: pool = pool_dic[db_name] else: @@ -60,11 +61,13 @@ def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False) pool.get('ir.cron')._poolJobs(db.dbname) return db, pool + def restart_pool(db_name, force_demo=False, update_module=False): # del db_dic[db_name] del pool_dic[db_name] return get_db_and_pool(db_name, force_demo, update_module=update_module) + def close_db(db_name): if db_name in db_dic: db_dic[db_name].truedb.close() @@ -72,6 +75,7 @@ def close_db(db_name): if db_name in pool_dic: del pool_dic[db_name] + def get_db_only(db_name): if db_name in db_dic: db = db_dic[db_name] @@ -80,10 +84,12 @@ def get_db_only(db_name): db_dic[db_name] = db return db + def get_db(db_name): # print "get_db", db_name return get_db_and_pool(db_name)[0] + def get_pool(db_name, force_demo=False, status=None, update_module=False): # print "get_pool", db_name pool = get_db_and_pool(db_name, force_demo, status, update_module)[1] @@ -94,6 +100,7 @@ def get_pool(db_name, force_demo=False, status=None, update_module=False): return pool # return get_db_and_pool(db_name)[1] + def init(): global db # db = get_db_only(tools.config['db_name']) diff --git a/bin/tinyerp-server.py b/bin/tinyerp-server.py index 3bef58fb0c9..969cd507965 100755 --- a/bin/tinyerp-server.py +++ b/bin/tinyerp-server.py @@ -50,7 +50,7 @@ __builtin__.__dict__['tinyerp_version_string'] = "Tiny ERP Server " + __version_ #---------------------------------------------------------- # python imports #---------------------------------------------------------- -import sys,os,signal +import sys, os, signal #---------------------------------------------------------- # ubuntu 8.04 has obsoleted `pyxml` package and installs here. # the path needs to be updated before any `import xml` @@ -75,9 +75,9 @@ logger = netsvc.Logger() import tools import time -if sys.platform=='win32': +if sys.platform == 'win32': import mx.DateTime - mx.DateTime.strptime = lambda x,y: mx.DateTime.mktime(time.strptime(x, y)) + mx.DateTime.strptime = lambda x, y: mx.DateTime.mktime(time.strptime(x, y)) #os.chdir(tools.file_path_root) @@ -104,9 +104,9 @@ try: except psycopg.OperationalError, err: logger.notifyChannel("init", netsvc.LOG_ERROR, "could not connect to database '%s'!" % (tools.config["db_name"],)) - msg = str(err).replace("FATAL:","").strip() + msg = str(err).replace("FATAL:", "").strip() db_msg = "database \"%s\" does not exist" % (tools.config["db_name"],) - + # Note: this is ugly but since psycopg only uses one exception for all errors # I don't think it's possible to do differently if msg == db_msg: @@ -165,7 +165,7 @@ if tools.config['upgrade']: tools.config['update'][m] = 1 #---------------------------------------------------------- -# import basic modules +# import basic modules #---------------------------------------------------------- import osv, workflow, report, service @@ -190,7 +190,7 @@ if tools.config["translate_out"]: buf = file(tools.config["translate_out"], "w") tools.trans_export(tools.config["language"], tools.config["translate_modules"], buf, fileformat) buf.close() - + logger.notifyChannel("init", netsvc.LOG_INFO, 'translation file written succesfully') sys.exit(0) @@ -217,12 +217,12 @@ if tools.config['xmlrpc']: sys.exit(1) interface = tools.config["interface"] secure = tools.config["secure"] - - httpd = netsvc.HttpDaemon(interface,port, secure) - + + httpd = netsvc.HttpDaemon(interface, port, secure) + if tools.config["xmlrpc"]: xml_gw = netsvc.xmlrpc.RpcGateway('web-services') - httpd.attach("/xmlrpc", xml_gw ) + httpd.attach("/xmlrpc", xml_gw) logger.notifyChannel("web-services", netsvc.LOG_INFO, "starting XML-RPC" + \ (tools.config['secure'] and ' Secure' or '') + \ @@ -242,10 +242,11 @@ if tools.config['netrpc']: logger.notifyChannel("init", netsvc.LOG_ERROR, "invalid port '%s'!" % (tools.config["netport"],)) sys.exit(1) netinterface = tools.config["netinterface"] - + tinySocket = netsvc.TinySocketServerThread(netinterface, netport, False) logger.notifyChannel("web-services", netsvc.LOG_INFO, "starting netrpc service, port "+str(netport)) + def handler(signum, frame): from tools import config if tools.config['netrpc']: @@ -259,8 +260,8 @@ def handler(signum, frame): from tools import config if config['pidfile']: - fd=open(config['pidfile'], 'w') - pidtext="%d" % (os.getpid()) + fd = open(config['pidfile'], 'w') + pidtext = "%d" % (os.getpid()) fd.write(pidtext) fd.close() From 9ff60dd68f13786cf9d327100d2c9599bd7acdca Mon Sep 17 00:00:00 2001 From: Olivier Laurent Date: Tue, 12 Aug 2008 16:49:42 +0200 Subject: [PATCH 010/169] removed wrong encoding line bzr revid: olt@tinyerp.com-20080812144942-7k5rsrnm0esfqxbw --- bin/osv/fields.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/osv/fields.py b/bin/osv/fields.py index f0de6e97bf1..dd97db25d5b 100644 --- a/bin/osv/fields.py +++ b/bin/osv/fields.py @@ -1,5 +1,4 @@ # -*- encoding: utf-8 -*- -# -*- encoding: iso-8859-1 -*- ############################################################################## # # Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved. From 9ab47ef0a722d806e21110fd5ee27acd25eb8634 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 12 Aug 2008 19:31:33 +0200 Subject: [PATCH 011/169] Fixed bug 254460 bzr revid: fp@tinyerp.com-20080812173133-i4pwzs5astcaibl2 --- addons/sale/partner.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/addons/sale/partner.py b/addons/sale/partner.py index 84805640811..790c0a69281 100644 --- a/addons/sale/partner.py +++ b/addons/sale/partner.py @@ -32,15 +32,15 @@ from osv import fields, osv -class res_partner(osv.osv): - _name = 'res.partner' - _inherit = 'res.partner' - _columns = { - 'sales': fields.one2many('sale.order', 'partner_id', 'Sales'), - } - -res_partner() - +#class res_partner(osv.osv): +# _name = 'res.partner' +# _inherit = 'res.partner' +# _columns = { +# 'sales': fields.one2many('sale.order', 'partner_id', 'Sales'), +# } +# +#res_partner() +# # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 440bd7f1d60ce8e9050abffb251637743b8820ad Mon Sep 17 00:00:00 2001 From: Jay Vora Date: Wed, 13 Aug 2008 11:14:08 +0530 Subject: [PATCH 012/169] Bugfix:expression.py:Added condition for a list with expression. bzr revid: jvo@tinyerp.com-20080813054408-pluxvra9zflezer6 --- bin/osv/expression.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/osv/expression.py b/bin/osv/expression.py index 050322a5e14..ee5b3c8a39c 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -81,6 +81,8 @@ class expression(object): if len(exp) == 1 and self._is_leaf(exp[0]): exp = exp[0] else: + if len(exp) == 3 and self._is_leaf(tuple(exp)): + exp=[exp] if not self._is_operator(exp[0][0]): if isinstance(exp[0],list): exp=tuple(exp[0]) From 50738738936feb3dafa709f8892462d674de8235 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Wed, 13 Aug 2008 10:42:42 +0200 Subject: [PATCH 013/169] osv.py come back bzr revid: christophe@tinyerp.com-20080813084242-x7q6wzjye96u23vy --- bin/osv/osv.py | 2585 ++++++------------------------------------------ 1 file changed, 284 insertions(+), 2301 deletions(-) diff --git a/bin/osv/osv.py b/bin/osv/osv.py index 7ce0c0e85b5..369f006c9df 100644 --- a/bin/osv/osv.py +++ b/bin/osv/osv.py @@ -28,2351 +28,334 @@ ############################################################################### # -# Object relationnal mapping to postgresql module -# . Hierarchical structure -# . Constraints consistency, validations -# . Object meta Data depends on its status -# . Optimised processing by complex query (multiple actions at once) -# . Default fields value -# . Permissions optimisation -# . Persistant object: DB postgresql -# . Datas conversions -# . Multi-level caching system -# . 2 different inheritancies -# . Fields: -# - classicals (varchar, integer, boolean, ...) -# - relations (one2many, many2one, many2many) -# - functions -# +# OSV: Objects Services # -import time -import types -from xml import dom, xpath +import orm import netsvc -import re +import pooler +import copy +import sys -import fields -import tools +import psycopg +from netsvc import Logger, LOG_ERROR +from tools.misc import UpdateableDict -from tools.config import config +module_list = [] +module_class_list = {} +class_pool = {} -regex_order = re.compile('^([a-zA-Z0-9_]+( desc)?( asc)?,?)+$', re.I) - - -def intersect(la, lb): - return filter(lambda x: x in lb, la) - - -class except_orm(Exception): - def __init__(self, name, value): +class except_osv(Exception): + def __init__(self, name, value, exc_type='warning'): self.name = name + self.exc_type = exc_type self.value = value - self.args = (name, value) + self.args = (exc_type,name) - -# Readonly python database object browser -class browse_null(object): +class osv_pool(netsvc.Service): def __init__(self): - self.id = False + self.obj_pool = {} + self.module_object_list = {} + self.created = [] + self._sql_error = {} + self._store_function = {} + netsvc.Service.__init__(self, 'object_proxy', audience='') + self.joinGroup('web-services') + self.exportMethod(self.exportedMethods) + self.exportMethod(self.obj_list) + self.exportMethod(self.exec_workflow) + self.exportMethod(self.execute) + self.exportMethod(self.execute_cr) - def __getitem__(self, name): - return False + def execute_cr(self, cr, uid, obj, method, *args, **kw): + try: + object = pooler.get_pool(cr.dbname).get(obj) + if not object: + self.abortResponse(1, 'Object Error', 'warning', + 'Object %s doesn\'t exist' % str(obj)) + return getattr(object,method)(cr, uid, *args, **kw) + except orm.except_orm, inst: + self.abortResponse(1, inst.name, 'warning', inst.value) + except except_osv, inst: + self.abortResponse(1, inst.name, inst.exc_type, inst.value) + except psycopg.IntegrityError, inst: + for key in self._sql_error.keys(): + if key in inst[0]: + self.abortResponse(1, 'Constraint Error', 'warning', + self._sql_error[key]) + self.abortResponse(1, 'Integrity Error', 'warning', inst[0]) + except Exception, e: + import traceback + tb_s = reduce(lambda x, y: x+y, traceback.format_exception( + sys.exc_type, sys.exc_value, sys.exc_traceback)) + logger = Logger() + logger.notifyChannel("web-services", LOG_ERROR, + 'Exception in call: ' + tb_s) + raise - def __int__(self): - return False - - def __str__(self): - return '' - - def __nonzero__(self): - return False - - -# -# TODO: execute an object method on browse_record_list -# -class browse_record_list(list): - - def __init__(self, lst, context=None): - if not context: - context = {} - super(browse_record_list, self).__init__(lst) - self.context = context - - -class browse_record(object): - def __init__(self, cr, uid, id, table, cache, context=None, list_class = None, fields_process={}): - ''' - table : the object (inherited from orm) - context : a dictionnary with an optionnal context - ''' - if not context: - context = {} - assert id, _('Wrong ID for the browse record, got %s, expected an integer.') % str(id) - self._list_class = list_class or browse_record_list - self._cr = cr - self._uid = uid - self._id = id - self._table = table - self._table_name = self._table._name - self._context = context - self._fields_process = fields_process - - cache.setdefault(table._name, {}) - self._data = cache[table._name] - if not id in self._data: - self._data[id] = {'id': id} - self._cache = cache - - def __getitem__(self, name): - if name == 'id': - return self._id - if not name in self._data[self._id]: - # build the list of fields we will fetch - - # fetch the definition of the field which was asked for - if name in self._table._columns: - col = self._table._columns[name] - elif name in self._table._inherit_fields: - col = self._table._inherit_fields[name][2] - elif hasattr(self._table, 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: - return getattr(self._table, name) - 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 - - # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields - if col._classic_write: - # gen the list of "local" (ie not inherited) fields which are classic or many2one - ffields = filter(lambda x: x[1]._classic_write, self._table._columns.items()) - # gen the list of inherited fields - inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items()) - # complete the field list with the inherited fields which are classic or many2one - ffields += filter(lambda x: x[1]._classic_write, inherits) - # otherwise we fetch only that field - else: - ffields = [(name, col)] - ids = filter(lambda id: not name in self._data[id], self._data.keys()) - # read the data - fffields = map(lambda x: x[0], ffields) - datas = self._table.read(self._cr, self._uid, ids, fffields, context=self._context, load="_classic_write") - if self._fields_process: - for n, f in ffields: - if f._type in self._fields_process: - for d in datas: - d[n] = self._fields_process[f._type](d[n]) - d[n].set_value(d[n], self, f) - - - # create browse records for 'remote' objects - for data in datas: - for n, f in ffields: - if f._type in ('many2one', 'one2one'): - if data[n]: - obj = self._table.pool.get(f._obj) - compids = False - if not f._classic_write: - ids2 = data[n][0] - else: - ids2 = data[n] - if ids2: - data[n] = browse_record(self._cr, self._uid, ids2, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) - else: - data[n] = browse_null() - else: - data[n] = browse_null() - 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) - return self._data[self._id][name] - - def __getattr__(self, name): -# raise an AttributeError exception. - return self[name] - - def __contains__(self, name): - return (name in self._table._columns) or (name in self._table._inherit_fields) or hasattr(self._table, name) - - def __hasattr__(self, name): - return name in self - - def __int__(self): - return self._id - - def __str__(self): - return "browse_record(%s, %d)" % (self._table_name, self._id) - - def __eq__(self, other): - return (self._table_name, self._id) == (other._table_name, other._id) - - def __ne__(self, other): - return (self._table_name, self._id) != (other._table_name, other._id) - - # we need to define __unicode__ even though we've already defined __str__ - # because we have overridden __getattr__ - def __unicode__(self): - return unicode(str(self)) - - def __hash__(self): - return hash((self._table_name, self._id)) - - __repr__ = __str__ - - -def get_pg_type(f): - ''' - returns a tuple - (type returned by postgres when the column was created, type expression to create the column) - ''' - - type_dict = { - fields.boolean: 'bool', - fields.integer: 'int4', - fields.text: 'text', - fields.date: 'date', - fields.time: 'time', - fields.datetime: 'timestamp', - fields.binary: 'bytea', - fields.many2one: 'int4', - } - if type(f) in type_dict: - f_type = (type_dict[type(f)], type_dict[type(f)]) - elif isinstance(f, fields.float): - if f.digits: - f_type = ('numeric', 'NUMERIC(%d,%d)' % (f.digits[0], f.digits[1])) - else: - f_type = ('float8', 'DOUBLE PRECISION') - elif isinstance(f, (fields.char, fields.reference)): - f_type = ('varchar', 'VARCHAR(%d)' % (f.size,)) - elif isinstance(f, fields.selection): - if isinstance(f.selection, list) and isinstance(f.selection[0][0], (str, unicode)): - f_size = reduce(lambda x, y: max(x, len(y[0])), f.selection, f.size or 16) - elif isinstance(f.selection, list) and isinstance(f.selection[0][0], int): - f_size = -1 - else: - f_size = (hasattr(f, 'size') and f.size) or 16 - - if f_size == -1: - f_type = ('int4', 'INTEGER') - else: - f_type = ('varchar', 'VARCHAR(%d)' % f_size) - elif isinstance(f, fields.function) and eval('fields.'+(f._type)) in type_dict: - t = eval('fields.'+(f._type)) - f_type = (type_dict[t], type_dict[t]) - elif isinstance(f, fields.function) and f._type == 'float': - f_type = ('float8', 'DOUBLE PRECISION') - elif isinstance(f, fields.function) and f._type == 'selection': - f_type = ('text', 'text') - else: - logger = netsvc.Logger() - logger.notifyChannel("init", netsvc.LOG_WARNING, '%s type not supported!' % (type(f))) - f_type = None - return f_type - - -class orm_template(object): - _name = None - _columns = {} - _constraints = [] - _defaults = {} - _rec_name = 'name' - _parent_name = 'parent_id' - _date_name = 'date' - _order = 'id' - _sequence = None - _description = None - _inherits = {} - _table = None - - def _field_create(self, cr, context={}): - cr.execute("SELECT id FROM ir_model WHERE model='%s'" % self._name) - if not cr.rowcount: - # reference model in order to have a description of its fonctionnality in custom_report - cr.execute('SELECT nextval(%s)', ('ir_model_id_seq',)) - id = cr.fetchone()[0] - cr.execute("INSERT INTO ir_model (id,model, name, info) VALUES (%s, %s, %s, %s)", (id, self._name, self._description, self.__doc__)) - if 'module' in context: - cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \ - ('model_'+self._table, context['module'], 'ir.model', id) - ) - cr.commit() - - cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", (self._name,)) - cols = {} - for rec in cr.dictfetchall(): - cols[rec['name']] = rec - - cr.execute("SELECT id FROM ir_model WHERE model='%s'" % self._name) - model_id = cr.fetchone()[0] - - for (k, f) in self._columns.items(): - vals = { - 'model_id': model_id, - 'model': self._name, - 'name': k, - 'field_description': f.string.replace("'", " "), - 'ttype': f._type, - 'relate': (f.relate and 1) or 0, - 'relation': f._obj or 'NULL', - 'group_name': f.group_name or '', - 'view_load': (f.view_load and 1) or 0, - 'select_level': str(f.select or 0) - } - if k not in cols: - cr.execute('select nextval(%s)', ('ir_model_fields_id_seq',)) - id = cr.fetchone()[0] - vals['id'] = id - cr.execute("""INSERT INTO ir_model_fields ( - id, model_id, model, name, field_description, ttype, - relate,relation,group_name,view_load,state,select_level - ) VALUES ( - %d,%s,%s,%s,%s,%s, %s,%s,%s,%s,%s, %s - )""", ( - id, vals['model_id'], vals['model'], vals['name'], vals['field_description'], vals['ttype'], - bool(vals['relate']), vals['relation'], vals['group_name'], bool(vals['view_load']), 'base', - vals['select_level'] - )) - if 'module' in context: - cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \ - (('field_'+self._table+'_'+k)[:64], context['module'], 'ir.model.fields', id) - ) - else: - for key, val in vals.items(): - if cols[k][key] != vals[key]: - print 'Different', cols[k][key], vals[key], k, key, self._name - cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name'])) - cr.commit() - cr.execute("""UPDATE ir_model_fields SET - model_id=%s, field_description=%s, ttype=%s, relate=%s, relation=%s, - group_name=%s, view_load=%s, select_level=%s - WHERE - model=%s AND name=%s""", ( - vals['model_id'], vals['field_description'], vals['ttype'], bool(vals['relate']), - vals['relation'], vals['group_name'], bool(vals['view_load']), - vals['select_level'], vals['model'], vals['name'] - )) - continue - cr.commit() - - def _auto_init(self, cr, context={}): - self._field_create(cr, context) - - def __init__(self, cr): - if not self._description: - self._description = self._name - if not self._table: - self._table = self._name.replace('.', '_') - - def browse(self, cr, uid, select, context=None, list_class=None, fields_process={}): - if not context: - context = {} - self._list_class = list_class or browse_record_list - cache = {} - # need to accepts ints and longs because ids coming from a method - # launched by button in the interface have a type long... - if isinstance(select, (int, long)): - return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) - elif isinstance(select, list): - return self._list_class([browse_record(cr, uid, id, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context) - else: - return browse_null() - - def __export_row(self, cr, uid, row, fields, context=None): - lines = [] - data = map(lambda x: '', range(len(fields))) - done = [] - for fpos in range(len(fields)): - f = fields[fpos] - if f: - r = row - i = 0 - while i < len(f): - r = r[f[i]] - if not r: - break - if isinstance(r, (browse_record_list, list)): - first = True - fields2 = map(lambda x: (x[:i+1]==f[:i+1] and x[i+1:]) \ - or [], fields) - if fields2 in done: - break - done.append(fields2) - for row2 in r: - lines2 = self.__export_row(cr, uid, row2, fields2, - context) - if first: - for fpos2 in range(len(fields)): - if lines2 and lines2[0][fpos2]: - data[fpos2] = lines2[0][fpos2] - lines += lines2[1:] - first = False - else: - lines += lines2 - break - i += 1 - if i == len(f): - data[fpos] = str(r or '') - return [data] + lines - - def export_data(self, cr, uid, ids, fields, context=None): - if not context: - context = {} - fields = map(lambda x: x.split('/'), fields) - datas = [] - for row in self.browse(cr, uid, ids, context): - datas += self.__export_row(cr, uid, row, fields, context) - return datas - - def import_data(self, cr, uid, fields, datas, mode='init', - current_module=None, noupdate=False, context=None): - if not context: - context = {} - fields = map(lambda x: x.split('/'), fields) - logger = netsvc.Logger() - - def process_liness(self, datas, prefix, fields_def, position=0): - line = datas[position] - row = {} - translate = {} - todo = [] - warning = '' - data_id = False - # - # Import normal fields - # - for i in range(len(fields)): - if i >= len(line): - raise Exception(_('Please check that all your lines have %d columns.') % (len(fields),)) - field = fields[i] - if field == ["id"]: - data_id = line[i] - continue - if (len(field)==len(prefix)+1) and field[len(prefix)].endswith(':id'): - res_id = False - if line[i]: - if fields_def[field[len(prefix)][:-3]]['type']=='many2many': - res_id = [] - for word in line[i].split(','): - if '.' in word: - module, xml_id = word.rsplit('.', 1) - else: - module, xml_id = current_module, word - ir_model_data_obj = self.pool.get('ir.model.data') - id = ir_model_data_obj._get_id(cr, uid, module, - xml_id) - res_id2 = ir_model_data_obj.read(cr, uid, [id], - ['res_id'])[0]['res_id'] - if res_id2: - res_id.append(res_id2) - if len(res_id): - res_id = [(6, 0, res_id)] - else: - if '.' in line[i]: - module, xml_id = line[i].rsplit('.', 1) - else: - module, xml_id = current_module, line[i] - ir_model_data_obj = self.pool.get('ir.model.data') - 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'] - row[field[0][:-3]] = res_id or False - continue - if (len(field) == len(prefix)+1) and \ - len(field[len(prefix)].split(':lang=')) == 2: - f, lang = field[len(prefix)].split(':lang=') - translate.setdefault(lang, {})[f]=line[i] or False - continue - if (len(field) == len(prefix)+1) and \ - (prefix == field[0:len(prefix)]): - if fields_def[field[len(prefix)]]['type'] == 'integer': - res = line[i] and int(line[i]) - elif fields_def[field[len(prefix)]]['type'] == 'float': - res = line[i] and float(line[i]) - elif fields_def[field[len(prefix)]]['type'] == 'selection': - res = False - if isinstance(fields_def[field[len(prefix)]]['selection'], - (tuple, list)): - sel = fields_def[field[len(prefix)]]['selection'] - else: - sel = fields_def[field[len(prefix)]]['selection'](self, - cr, uid, context) - for key, val in sel: - if str(key) == line[i]: - res = key - if line[i] and not res: - logger.notifyChannel("import", netsvc.LOG_WARNING, - "key '%s' not found in selection field '%s'" % \ - (line[i], field[len(prefix)])) - elif fields_def[field[len(prefix)]]['type']=='many2one': - res = False - if line[i]: - relation = fields_def[field[len(prefix)]]['relation'] - res2 = self.pool.get(relation).name_search(cr, uid, - line[i], [], operator='=') - res = (res2 and res2[0][0]) or False - if not res: - warning += ('Relation not found: ' + line[i] + \ - ' on ' + relation + ' !\n') - logger.notifyChannel("import", netsvc.LOG_WARNING, - 'Relation not found: ' + line[i] + \ - ' on ' + relation + ' !\n') - elif fields_def[field[len(prefix)]]['type']=='many2many': - res = [] - if line[i]: - relation = fields_def[field[len(prefix)]]['relation'] - for word in line[i].split(','): - res2 = self.pool.get(relation).name_search(cr, - uid, word, [], operator='=') - res3 = (res2 and res2[0][0]) or False - if not res3: - warning += ('Relation not found: ' + \ - line[i] + ' on '+relation + ' !\n') - logger.notifyChannel("import", - netsvc.LOG_WARNING, - 'Relation not found: ' + line[i] + \ - ' on '+relation + ' !\n') - else: - res.append(res3) - if len(res): - res = [(6, 0, res)] - else: - res = line[i] or False - row[field[len(prefix)]] = res - elif (prefix==field[0:len(prefix)]): - if field[0] not in todo: - todo.append(field[len(prefix)]) - # - # Import one2many fields - # - nbrmax = 1 - for field in todo: - newfd = self.pool.get(fields_def[field]['relation']).fields_get( - cr, uid, context=context) - res = process_liness(self, datas, prefix + [field], newfd, position) - (newrow, max2, w2, translate2, data_id2) = res - nbrmax = max(nbrmax, max2) - warning = warning + w2 - reduce(lambda x, y: x and y, newrow) - row[field] = (reduce(lambda x, y: x or y, newrow.values()) and \ - [(0, 0, newrow)]) or [] - i = max2 - while (position+i)\n' - '\n' - ' \n' - '') % (self._rec_name) - - return arch - - # - # if view_id, view_type is not required - # - def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False): - if not context: - context = {} - def _inherit_apply(src, inherit): - def _find(node, node2): - # Check if xpath query or normal inherit (with field matching) - if node2.nodeType == node2.ELEMENT_NODE and node2.localName == 'xpath': - res = xpath.Evaluate(node2.getAttribute('expr'), node) - return res and res[0] - else: - if node.nodeType == node.ELEMENT_NODE and node.localName == node2.localName: - res = True - for attr in node2.attributes.keys(): - if attr == 'position': - continue - if node.hasAttribute(attr): - if node.getAttribute(attr)==node2.getAttribute(attr): - continue - res = False - if res: - return node - for child in node.childNodes: - res = _find(child, node2) - if res: - return res - return None - - doc_src = dom.minidom.parseString(src) - doc_dest = dom.minidom.parseString(inherit) - toparse = doc_dest.childNodes - while len(toparse): - node2 = toparse.pop(0) - if not node2.nodeType == node2.ELEMENT_NODE: - continue - if node2.localName == 'data': - toparse += node2.childNodes - continue - node = _find(doc_src, node2) - if node: - pos = 'inside' - if node2.hasAttribute('position'): - pos = node2.getAttribute('position') - if pos == 'replace': - parent = node.parentNode - for child in node2.childNodes: - if child.nodeType == child.ELEMENT_NODE: - parent.insertBefore(child, node) - parent.removeChild(node) - else: - for child in node2.childNodes: - if child.nodeType == child.ELEMENT_NODE: - if pos == 'inside': - node.appendChild(child) - elif pos == 'after': - sib = node.nextSibling - if sib: - node.parentNode.insertBefore(child, sib) - else: - node.parentNode.appendChild(child) - elif pos=='before': - node.parentNode.insertBefore(child, node) - else: - raise AttributeError(_('Unknown position in inherited view %s !') % pos) - else: - attrs = ''.join([ - ' %s="%s"' % (attr, node2.getAttribute(attr)) - for attr in node2.attributes.keys() - if attr != 'position' - ]) - tag = "<%s%s>" % (node2.localName, attrs) - raise AttributeError(_("Couldn't find tag '%s' in parent view !") % tag) - return doc_src.toxml(encoding="utf-8").replace('\t', '') - - result = {'type': view_type, 'model': self._name} - - ok = True - model = True - sql_res = False - while ok: - if view_id: - where = (model and (" and model='%s'" % (self._name,))) or '' - cr.execute('SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE id=%d'+where, (view_id,)) - else: - cr.execute('SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE model=%s AND type=%s ORDER BY priority', (self._name, view_type)) - sql_res = cr.fetchone() - if not sql_res: - break - ok = sql_res[5] - view_id = ok or sql_res[3] - model = False - - # if a view was found - if sql_res: - result['type'] = sql_res[4] - result['view_id'] = sql_res[3] - result['arch'] = sql_res[0] - - def _inherit_apply_rec(result, inherit_id): - # get all views which inherit from (ie modify) this view - cr.execute('select arch,id from ir_ui_view where inherit_id=%d and model=%s order by priority', (inherit_id, self._name)) - sql_inherit = cr.fetchall() - for (inherit, id) in sql_inherit: - result = _inherit_apply(result, inherit) - result = _inherit_apply_rec(result, id) - return result - - result['arch'] = _inherit_apply_rec(result['arch'], sql_res[3]) - - result['name'] = sql_res[1] - result['field_parent'] = sql_res[2] or False - else: - # otherwise, build some kind of default view - if view_type == 'form': - res = self.fields_get(cr, user, context=context) - xml = '''''' \ - '''
''' % (self._description,) - for x in res: - if res[x]['type'] not in ('one2many', 'many2many'): - xml += '' % (x,) - if res[x]['type'] == 'text': - xml += "" - xml += "" - elif view_type == 'tree': - xml = '''''' \ - '''''' \ - % (self._description, self._rec_name) - elif view_type == 'calendar': - xml = self.__get_default_calendar_view() - else: - xml = '' - result['arch'] = xml - result['name'] = 'default' - result['field_parent'] = False - result['view_id'] = 0 - - doc = dom.minidom.parseString(result['arch'].encode('utf-8')) - xarch, xfields = self.__view_look_dom_arch(cr, user, doc, context=context) - result['arch'] = xarch - result['fields'] = xfields - if toolbar: - def clean(x): - x = x[2] - for key in ('report_sxw_content', 'report_rml_content', - 'report_sxw', 'report_rml', - 'report_sxw_content_data', 'report_rml_content_data'): - if key in x: - del x[key] - return x - ir_values_obj = self.pool.get('ir.values') - resprint = ir_values_obj.get(cr, user, 'action', - 'client_print_multi', [(self._name, False)], False, - context) - resaction = ir_values_obj.get(cr, user, 'action', - 'client_action_multi', [(self._name, False)], False, - context) - - resrelate = ir_values_obj.get(cr, user, 'action', - 'client_action_relate', [(self._name, False)], False, - context) - resprint = map(clean, resprint) - resaction = map(clean, resaction) - resaction = filter(lambda x: not x.get('multi', False), resaction) - resprint = filter(lambda x: not x.get('multi', False), resprint) - resrelate = map(lambda x: x[2], resrelate) - - for x in resprint+resaction+resrelate: - x['string'] = x['name'] - - result['toolbar'] = { - 'print': resprint, - 'action': resaction, - 'relate': resrelate - } - return result - - _view_look_dom_arch = __view_look_dom_arch - - def search_count(self, cr, user, args, context=None): - if not context: - context = {} - res = self.search(cr, user, args, context=context, count=True) - if isinstance(res, list): - return len(res) + def exec_workflow(self, db, uid, obj, method, *args): + cr = pooler.get_db(db).cursor() + try: + try: + res = self.exec_workflow_cr(cr, uid, obj, method, *args) + cr.commit() + except orm.except_orm, inst: + cr.rollback() + self.abortResponse(1, inst.name, 'warning', inst.value) + except except_osv, inst: + cr.rollback() + self.abortResponse(1, inst.name, inst[0], inst.value) + finally: + cr.close() return res - def search(self, cr, user, args, offset=0, limit=None, order=None, - context=None, count=False): - raise _('The search method is not implemented on this object !') + def obj_list(self): + return self.obj_pool.keys() - def name_get(self, cr, user, ids, context=None): - raise _('The name_get method is not implemented on this object !') + # adds a new object instance to the object pool. + # if it already existed, the instance is replaced + def add(self, name, obj_inst): + if self.obj_pool.has_key(name): + del self.obj_pool[name] + self.obj_pool[name] = obj_inst - def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=80): - raise _('The name_search method is not implemented on this object !') + module = str(obj_inst.__class__)[6:] + module = module[:len(module)-1] + module = module.split('.')[0][2:] + self.module_object_list.setdefault(module, []).append(obj_inst) - def copy(self, cr, uid, id, default=None, context=None): - raise _('The copy method is not implemented on this object !') + def get(self, name): + obj = self.obj_pool.get(name, None) +# We cannot uncomment this line because it breaks initialisation since objects do not initialize +# in the correct order and the ORM doesnt support correctly when some objets do not exist yet +# assert obj, "object %s does not exist !" % name + return obj + + #TODO: pass a list of modules to load + def instanciate(self, module, cr): +# print "module list:", module_list +# for module in module_list: + res = [] + class_list = module_class_list.get(module, []) +# if module not in self.module_object_list: +# print "%s class_list:" % module, class_list + for klass in class_list: + res.append(klass.createInstance(self, module, cr)) + return res +# else: +# print "skipping module", module + +#pooler.get_pool(cr.dbname) = osv_pool() + +class osv_memory(orm.orm_memory): + #__metaclass__ = inheritor + def __new__(cls): + module = str(cls)[6:] + module = module[:len(module)-1] + module = module.split('.')[0][2:] + if not hasattr(cls, '_module'): + cls._module = module + module_class_list.setdefault(cls._module, []).append(cls) + class_pool[cls._name] = cls + if module not in module_list: + module_list.append(cls._module) + return None + + # + # Goal: try to apply inheritancy at the instanciation level and + # put objects in the pool var + # + def createInstance(cls, pool, module, cr): + name = hasattr(cls,'_name') and cls._name or cls._inherit + parent_name = hasattr(cls, '_inherit') and cls._inherit + if parent_name: + print 'Inherit not supported in osv_memory object !' + obj = object.__new__(cls) + obj.__init__(pool, cr) + return obj + createInstance = classmethod(createInstance) + + def __init__(self, pool, cr): + pool.add(self._name, self) + self.pool = pool + orm.orm_memory.__init__(self, cr) -class orm_memory(orm_template): - _protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count'] - _inherit_fields = {} - _max_count = 200 - _max_hours = 1 - _check_time = 20 - def __init__(self, cr): - super(orm_memory, self).__init__(cr) - self.datas = {} - self.next_id = 0 - self.check_id = 0 - cr.execute('delete from wkf_instance where res_type=%s', (self._name,)) +class osv(orm.orm): + #__metaclass__ = inheritor + def __new__(cls): + module = str(cls)[6:] + module = module[:len(module)-1] + module = module.split('.')[0][2:] + if not hasattr(cls, '_module'): + cls._module = module + module_class_list.setdefault(cls._module, []).append(cls) + class_pool[cls._name] = cls + if module not in module_list: + module_list.append(cls._module) + return None + + # + # Goal: try to apply inheritancy at the instanciation level and + # put objects in the pool var + # + def createInstance(cls, pool, module, cr): + parent_name = hasattr(cls, '_inherit') and cls._inherit + if parent_name: + parent_class = pool.get(parent_name).__class__ + assert parent_class, "parent class %s does not exist !" % parent_name + nattr = {} + for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'): + new = copy.copy(getattr(pool.get(parent_name), s)) + if hasattr(new, 'update'): + new.update(cls.__dict__.get(s, {})) + else: + new.extend(cls.__dict__.get(s, [])) + nattr[s] = new + name = hasattr(cls,'_name') and cls._name or cls._inherit + cls = type(name, (cls, parent_class), nattr) + obj = object.__new__(cls) + obj.__init__(pool, cr) + return obj + createInstance = classmethod(createInstance) + + def __init__(self, pool, cr): + pool.add(self._name, self) + self.pool = pool + orm.orm.__init__(self, cr) + +class Cacheable(object): + + _cache = UpdateableDict() + + def add(self, key, value): + self._cache[key] = value + + def invalidate(self, key): + del self._cache[key] + + def get(self, key): + try: + w = self._cache[key] + return w + except KeyError: + return None def clear(self): - self.check_id += 1 - if self.check_id % self._check_time: - return True - tounlink = [] - max = time.time() - self._max_hours * 60 * 60 - for id in self.datas: - if self.datas[id]['internal.date_access'] < max: - tounlink.append(id) - self.unlink(tounlink) - if len(self.datas)>self._max_count: - sorted = map(lambda x: (x[1]['internal.date_access'], x[0]), self.datas.items()) - sorted.sort() - ids = map(lambda x: x[1], sorted[:len(self.datas)-self._max_count]) - self.unlink(ids) - return True + self._cache.clear() + self._items = [] + +def filter_dict(d, fields): + res = {} + for f in fields + ['id']: + if f in d: + res[f] = d[f] + return res + +class cacheable_osv(osv, Cacheable): + + _relevant = ['lang'] + + def __init__(self): + super(cacheable_osv, self).__init__() def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'): if not fields: - fields = self._columns.keys() - result = [] + fields=[] + if not context: + context={} + fields = fields or self._columns.keys() + ctx = [context.get(x, False) for x in self._relevant] + result, tofetch = [], [] for id in ids: - r = {'id': id} - for f in fields: - r[f] = self.datas[id].get(f, False) - result.append(r) - self.datas[id]['internal.date_access'] = time.time() - fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields) - for f in fields_post: - res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=False) - for record in result: - record[f] = res2[record['id']] - return result - - def write(self, cr, user, ids, vals, context=None): - vals2 = {} - upd_todo = [] - for field in vals: - if self._columns[field]._classic_write: - vals2[field] = vals[field] + res = self.get(self._name, id, ctx) + if not res: + tofetch.append(id) else: - upd_todo.append(field) - for id_new in ids: - self.datas[id_new].update(vals2) - self.datas[id_new]['internal.date_access'] = time.time() - for field in upd_todo: - self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context) - self._validate(cr, user, [id_new], context) - wf_service = netsvc.LocalService("workflow") - wf_service.trg_write(user, self._name, id_new, cr) - self.clear() - return id_new + result.append(filter_dict(res, fields)) - def create(self, cr, user, vals, context=None): - self.next_id += 1 - id_new = self.next_id - default = [] - for f in self._columns.keys(): - if not f in vals: - default.append(f) - if len(default): - vals.update(self.default_get(cr, user, default, context)) - vals2 = {} - upd_todo = [] - for field in vals: - if self._columns[field]._classic_write: - vals2[field] = vals[field] - else: - upd_todo.append(field) - self.datas[id_new] = vals2 - self.datas[id_new]['internal.date_access'] = time.time() - for field in upd_todo: - self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context) - self._validate(cr, user, [id_new], context) - wf_service = netsvc.LocalService("workflow") - wf_service.trg_create(user, self._name, id_new, cr) - self.clear() - return id_new + # gen the list of "local" (ie not inherited) fields which are classic or many2one + nfields = filter(lambda x: x[1]._classic_write, self._columns.items()) + # gen the list of inherited fields + inherits = map(lambda x: (x[0], x[1][2]), self._inherit_fields.items()) + # complete the field list with the inherited fields which are classic or many2one + nfields += filter(lambda x: x[1]._classic_write, inherits) + nfields = [x[0] for x in nfields] - def default_get(self, cr, uid, fields_list, context=None): - if not context: - context = {} - value = {} - # get the default values for the inherited fields - for f in fields_list: - if f in self._defaults: - value[f] = self._defaults[f](self, cr, uid, context) - fld_def = ((f in self._columns) and self._columns[f]) \ - or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \ - or False - - # get the default values set by the user and override the default - # values defined in the object - ir_values_obj = self.pool.get('ir.values') - res = ir_values_obj.get(cr, uid, 'default', False, [self._name]) - for id, field, field_value in res: - if field in fields_list: - fld_def = (field in self._columns) - if fld_def._type in ('many2one', 'one2one'): - obj = self.pool.get(fld_def._obj) - if not obj.search(cr, uid, [('id', '=', field_value)]): - continue - if fld_def._type in ('many2many'): - obj = self.pool.get(fld_def._obj) - field_value2 = [] - for i in range(len(field_value)): - if not obj.search(cr, uid, [('id', '=', - field_value[i])]): - continue - field_value2.append(field_value[i]) - field_value = field_value2 - if fld_def._type in ('one2many'): - obj = self.pool.get(fld_def._obj) - field_value2 = [] - for i in range(len(field_value)): - field_value2.append({}) - for field2 in field_value[i]: - if obj._columns[field2]._type in ('many2one', 'one2one'): - obj2 = self.pool.get(obj._columns[field2]._obj) - if not obj2.search(cr, uid, - [('id', '=', field_value[i][field2])]): - continue - # TODO add test for many2many and one2many - field_value2[i][field2] = field_value[i][field2] - field_value = field_value2 - value[field] = field_value - return value - - def search(self, cr, user, args, offset=0, limit=None, order=None, - context=None, count=False): - return self.datas.keys() - - def unlink(self, cr, uid, ids, context=None): - for id in ids: - if id in self.datas: - del self.datas[id] - if len(ids): - cr.execute('delete from wkf_instance where res_type=%s and res_id in ('+','.join(map(str, ids))+')', (self._name, )) - return True - - def perm_read(self, cr, user, ids, context=None, details=True): - result = [] - for id in ids: - result.append({ - 'create_uid': (user, 'Root'), - 'create_date': time.strftime('%Y-%m-%d %H:%M:%S'), - 'write_uid': False, - 'write_date': False, - 'id': id - }) - return result - -class orm(orm_template): - - _sql_constraints = [] - - _log_access = True - _table = None - _protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count'] - def _auto_init(self, cr, context={}): - logger = netsvc.Logger() - create = False - self._field_create(cr, context=context) - if not hasattr(self, "_auto") or self._auto: - cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table) - if not cr.rowcount: - cr.execute("CREATE TABLE \"%s\" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITH OIDS" % self._table) - create = True - cr.commit() - if self._log_access: - logs = { - 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL', - 'create_date': 'TIMESTAMP', - 'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL', - 'write_date': 'TIMESTAMP' - } - for k in logs: - cr.execute( - """ - SELECT c.relname - FROM pg_class c, pg_attribute a - WHERE c.relname='%s' AND a.attname='%s' AND c.oid=a.attrelid - """ % (self._table, k)) - if not cr.rowcount: - cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" %s" % - (self._table, k, logs[k])) - cr.commit() - - # iterate on the database columns to drop the NOT NULL constraints - # of fields which were required but have been removed - cr.execute( - "SELECT a.attname, a.attnotnull "\ - "FROM pg_class c, pg_attribute a "\ - "WHERE c.oid=a.attrelid AND c.relname='%s'" % self._table) - db_columns = cr.dictfetchall() - for column in db_columns: - if column['attname'] not in ('id', 'oid', 'tableoid', 'ctid', 'xmin', 'xmax', 'cmin', 'cmax'): - if column['attnotnull'] and column['attname'] not in self._columns: - cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" DROP NOT NULL" % (self._table, column['attname'])) - - # iterate on the "object columns" - for k in self._columns: - if k in ('id', 'write_uid', 'write_date', 'create_uid', 'create_date'): - continue - #raise _('Can not define a column %s. Reserved keyword !') % (k,) - f = self._columns[k] - - if isinstance(f, fields.one2many): - cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname=%s", (f._obj,)) - if cr.fetchone(): - cr.execute("SELECT count(*) as c FROM pg_class c,pg_attribute a WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid", (f._obj, f._fields_id)) - res = cr.fetchone()[0] - if not res: - cr.execute("ALTER TABLE \"%s\" ADD FOREIGN KEY (%s) REFERENCES \"%s\" ON DELETE SET NULL" % (self._obj, f._fields_id, f._table)) - 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) 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.commit() - else: - cr.execute("SELECT c.relname,a.attname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,t.typname,CASE WHEN a.attlen=-1 THEN a.atttypmod-4 ELSE a.attlen END as size FROM pg_class c,pg_attribute a,pg_type t WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid AND a.atttypid=t.oid", (self._table, k)) - res = cr.dictfetchall() - if not res: - if not isinstance(f, fields.function) or f.store: - - # add the missing field - cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" %s" % (self._table, k, get_pg_type(f)[1])) - - # initialize it - if not create and k in self._defaults: - default = self._defaults[k](self, cr, 1, {}) - if not default: - cr.execute("UPDATE \"%s\" SET \"%s\"=NULL" % (self._table, k)) - else: - cr.execute("UPDATE \"%s\" SET \"%s\"='%s'" % (self._table, k, default)) - if isinstance(f, fields.function): - cr.execute('select id from '+self._table) - ids_lst = map(lambda x: x[0], cr.fetchall()) - while ids_lst: - iids = ids_lst[:40] - ids_lst = ids_lst[40:] - res = f.get(cr, self, iids, k, 1, {}) - for r in res.items(): - cr.execute("UPDATE \"%s\" SET \"%s\"='%s' where id=%d"% (self._table, k, r[1], r[0])) - - # 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('.', '_') - # 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)) - if f.select: - cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (self._table, k, self._table, k)) - if f.required: - cr.commit() - try: - cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" SET NOT NULL" % (self._table, k)) - except: - logger.notifyChannel('init', netsvc.LOG_WARNING, 'WARNING: unable to set column %s of table %s not null !\nTry to re-run: tinyerp-server.py --update=module\nIf it doesn\'t work, update records and execute manually:\nALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % (k, self._table, self._table, k)) - cr.commit() - elif len(res)==1: - f_pg_def = res[0] - f_pg_type = f_pg_def['typname'] - f_pg_size = f_pg_def['size'] - f_pg_notnull = f_pg_def['attnotnull'] - if isinstance(f, fields.function) and not f.store: - logger.notifyChannel('init', netsvc.LOG_WARNING, 'column %s (%s) in table %s was converted to a function !\nYou should remove this column from your database.' % (k, f.string, self._table)) - f_obj_type = None - else: - f_obj_type = get_pg_type(f) and get_pg_type(f)[0] - - if f_obj_type: - if f_pg_type != f_obj_type: - logger.notifyChannel('init', netsvc.LOG_WARNING, "column '%s' in table '%s' has changed type (DB = %s, def = %s) !" % (k, self._table, f_pg_type, f._type)) - if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size != f.size: - # columns with the name 'type' cannot be changed for an unknown reason?! - if k != 'type': - if f_pg_size > f.size: - logger.notifyChannel('init', netsvc.LOG_WARNING, "column '%s' in table '%s' has changed size (DB = %d, def = %d), DB size will be kept !" % (k, self._table, f_pg_size, f.size)) - # If actual DB size is < than new - # We update varchar size, otherwise, we keep DB size - # to avoid truncated string... - if f_pg_size < f.size: - cr.execute("ALTER TABLE \"%s\" RENAME COLUMN \"%s\" TO temp_change_size" % (self._table, k)) - cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" VARCHAR(%d)" % (self._table, k, f.size)) - cr.execute("UPDATE \"%s\" SET \"%s\"=temp_change_size::VARCHAR(%d)" % (self._table, k, f.size)) - cr.execute("ALTER TABLE \"%s\" DROP COLUMN temp_change_size" % (self._table,)) - cr.commit() - # if the field is required and hasn't got a NOT NULL constraint - if f.required and f_pg_notnull == 0: - # set the field to the default value if any - if k in self._defaults: - default = self._defaults[k](self, cr, 1, {}) - if not (default is False): - cr.execute("UPDATE \"%s\" SET \"%s\"='%s' WHERE %s is NULL" % (self._table, k, default, k)) - cr.commit() - # add the NOT NULL constraint - try: - cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" SET NOT NULL" % (self._table, k)) - cr.commit() - except: - logger.notifyChannel('init', netsvc.LOG_WARNING, 'unable to set a NOT NULL constraint on column %s of the %s table !\nIf you want to have it, you should update the records and execute manually:\nALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % (k, self._table, self._table, k)) - cr.commit() - elif not f.required and f_pg_notnull == 1: - cr.execute("ALTER TABLE \"%s\" ALTER COLUMN \"%s\" DROP NOT NULL" % (self._table, k)) - cr.commit() - cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = '%s_%s_index' and tablename = '%s'" % (self._table, k, self._table)) - res = cr.dictfetchall() - if not res and f.select: - cr.execute("CREATE INDEX \"%s_%s_index\" ON \"%s\" (\"%s\")" % (self._table, k, self._table, k)) - cr.commit() - if res and not f.select: - cr.execute("DROP INDEX \"%s_%s_index\"" % (self._table, k)) - cr.commit() - if isinstance(f, fields.many2one): - ref = self.pool.get(f._obj)._table - if ref != 'ir_actions': - cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, ' \ - 'pg_attribute as att1, pg_attribute as att2 ' \ - 'WHERE con.conrelid = cl1.oid ' \ - 'AND cl1.relname = %s ' \ - 'AND con.confrelid = cl2.oid ' \ - 'AND cl2.relname = %s ' \ - 'AND array_lower(con.conkey, 1) = 1 ' \ - 'AND con.conkey[1] = att1.attnum ' \ - 'AND att1.attrelid = cl1.oid ' \ - 'AND att1.attname = %s ' \ - 'AND array_lower(con.confkey, 1) = 1 ' \ - 'AND con.confkey[1] = att2.attnum ' \ - 'AND att2.attrelid = cl2.oid ' \ - 'AND att2.attname = %s ' \ - 'AND con.contype = \'f\'', (self._table, ref, k, 'id')) - res = cr.dictfetchall() - if res: - confdeltype = { - 'RESTRICT': 'r', - 'NO ACTION': 'a', - 'CASCADE': 'c', - 'SET NULL': 'n', - 'SET DEFAULT': 'd', - } - if res[0]['confdeltype'] != confdeltype.get(f.ondelete.upper(), 'a'): - cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res[0]['conname'] + '"') - cr.execute('ALTER TABLE "' + self._table + '" ADD FOREIGN KEY ("' + k + '") REFERENCES "' + ref + '" ON DELETE ' + f.ondelete) - cr.commit() - else: - print "ERROR" - else: - cr.execute("SELECT relname FROM pg_class WHERE relkind in ('r','v') AND relname='%s'" % self._table) - create = not bool(cr.fetchone()) - - for (key, con, _) in self._sql_constraints: - cr.execute("SELECT conname FROM pg_constraint where conname='%s_%s'" % (self._table, key)) - if not cr.dictfetchall(): - try: - cr.execute('alter table \"%s\" add constraint \"%s_%s\" %s' % (self._table, self._table, key, con,)) - cr.commit() - except: - logger.notifyChannel('init', netsvc.LOG_WARNING, 'unable to add \'%s\' constraint on table %s !\n If you want to have it, you should update the records and execute manually:\nALTER table %s ADD CONSTRAINT %s_%s %s' % (con, self._table, self._table, self._table, key, con,)) - - if create: - if hasattr(self, "_sql"): - for line in self._sql.split(';'): - line2 = line.replace('\n', '').strip() - if line2: - cr.execute(line2) - cr.commit() - - def __init__(self, cr): - super(orm, self).__init__(cr) - f = filter(lambda a: isinstance(self._columns[a], fields.function) and self._columns[a].store, self._columns) - if f: - list_store = [] - tuple_store = () - tuple_fn = () - for store_field in f: - if not self._columns[store_field].store == True: - dict_store = self._columns[store_field].store - key = dict_store.keys() - list_data = [] - for i in key: - tuple_store = self._name, store_field, self._columns[store_field]._fnct.__name__, tuple(dict_store[i][0]), dict_store[i][1], i - list_data.append(tuple_store) - #tuple_store=self._name,store_field,self._columns[store_field]._fnct.__name__,tuple(dict_store[key[0]][0]),dict_store[key[0]][1] - for l in list_data: - list_store = [] - if l[5] in self.pool._store_function.keys(): - self.pool._store_function[l[5]].append(l) - temp_list = list(set(self.pool._store_function[l[5]])) - self.pool._store_function[l[5]] = temp_list - else: - list_store.append(l) - self.pool._store_function[l[5]] = list_store - - for (key, _, msg) in self._sql_constraints: - self.pool._sql_error[self._table+'_'+key] = msg - - # Load manual fields - - cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields')) - if cr.fetchone(): - cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual')) - for field in cr.dictfetchall(): - if field['name'] in self._columns: - continue - attrs = { - 'string': field['field_description'], - 'required': bool(field['required']), - 'readonly': bool(field['readonly']), - 'domain': field['domain'] or None, - 'size': field['size'], - 'ondelete': field['on_delete'], - 'translate': (field['translate']), - #'select': int(field['select_level']) - } - #if field['relation']: - # attrs['relation'] = field['relation'] - if field['ttype'] == 'selection': - self._columns[field['name']] = getattr(fields, field['ttype'])(eval(field['selection']), **attrs) - elif field['ttype'] == 'many2one': - self._columns[field['name']] = getattr(fields, field['ttype'])(field['relation'], **attrs) - else: - self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs) - - self._inherits_reload() - if not self._sequence: - self._sequence = self._table+'_id_seq' - for k in self._defaults: - assert (k in self._columns) or (k in self._inherit_fields), 'Default function defined in %s but field %s does not exist !' % (self._name, k,) - for f in self._columns: - self._columns[f].restart() - - def default_get(self, cr, uid, fields_list, context=None): - if not context: - context = {} - value = {} - # get the default values for the inherited fields - for t in self._inherits.keys(): - value.update(self.pool.get(t).default_get(cr, uid, fields_list, - context)) - - # get the default values defined in the object - for f in fields_list: - if f in self._defaults: - value[f] = self._defaults[f](self, cr, uid, context) - fld_def = ((f in self._columns) and self._columns[f]) \ - or ((f in self._inherit_fields) and self._inherit_fields[f][2]) \ - or False - if isinstance(fld_def, fields.property): - property_obj = self.pool.get('ir.property') - definition_id = fld_def._field_get(cr, uid, self._name, f) - nid = property_obj.search(cr, uid, [('fields_id', '=', - definition_id), ('res_id', '=', False)]) - if nid: - prop_value = property_obj.browse(cr, uid, nid[0], - context=context).value - value[f] = (prop_value and int(prop_value.split(',')[1])) \ - or False - - # get the default values set by the user and override the default - # values defined in the object - ir_values_obj = self.pool.get('ir.values') - res = ir_values_obj.get(cr, uid, 'default', False, [self._name]) - for id, field, field_value in res: - if field in fields_list: - fld_def = (field in self._columns) and self._columns[field] or self._inherit_fields[field][2] - if fld_def._type in ('many2one', 'one2one'): - obj = self.pool.get(fld_def._obj) - if not obj.search(cr, uid, [('id', '=', field_value)]): - continue - if fld_def._type in ('many2many'): - obj = self.pool.get(fld_def._obj) - field_value2 = [] - for i in range(len(field_value)): - if not obj.search(cr, uid, [('id', '=', - field_value[i])]): - continue - field_value2.append(field_value[i]) - field_value = field_value2 - if fld_def._type in ('one2many'): - obj = self.pool.get(fld_def._obj) - field_value2 = [] - for i in range(len(field_value)): - field_value2.append({}) - for field2 in field_value[i]: - if obj._columns[field2]._type in ('many2one', 'one2one'): - obj2 = self.pool.get(obj._columns[field2]._obj) - if not obj2.search(cr, uid, - [('id', '=', field_value[i][field2])]): - continue - # TODO add test for many2many and one2many - field_value2[i][field2] = field_value[i][field2] - field_value = field_value2 - value[field] = field_value - return value - - - # - # Update objects that uses this one to update their _inherits fields - # - def _inherits_reload_src(self): - for obj in self.pool.obj_pool.values(): - if self._name in obj._inherits: - obj._inherits_reload() - - def _inherits_reload(self): - res = {} - for table in self._inherits: - res.update(self.pool.get(table)._inherit_fields) - for col in self.pool.get(table)._columns.keys(): - res[col] = (table, self._inherits[table], self.pool.get(table)._columns[col]) - for col in self.pool.get(table)._inherit_fields.keys(): - res[col] = (table, self._inherits[table], self.pool.get(table)._inherit_fields[col][2]) - self._inherit_fields = res - self._inherits_reload_src() - - def fields_get(self, cr, user, fields=None, context=None): - read_access = self.pool.get('ir.model.access').check(cr, user, self._name, 'write', raise_exception=False) - return super(orm, self).fields_get(cr, user, fields, context, read_access) - - def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'): - if not context: - context = {} - self.pool.get('ir.model.access').check(cr, user, self._name, 'read') - if not fields: - fields = self._columns.keys() + self._inherit_fields.keys() - select = ids - if isinstance(ids, (int, long)): - select = [ids] - result = self._read_flat(cr, user, select, fields, context, load) - for r in result: - for key, v in r.items(): - if v == None: - r[key] = False - if isinstance(ids, (int, long)): - return result[0] - return result - - def _read_flat(self, cr, user, ids, fields, context=None, load='_classic_read'): - if not context: - context = {} - if not ids: - return [] - - if fields == None: - fields = self._columns.keys() - - # construct a clause for the rules : - d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name) - - # all inherited fields + all non inherited fields for which the attribute whose name is in load is True - fields_pre = filter(lambda x: x in self._columns and getattr(self._columns[x], '_classic_write'), fields) + self._inherits.values() - - res = [] - if len(fields_pre): - fields_pre2 = map(lambda x: (x in ('create_date', 'write_date')) and ('date_trunc(\'second\', '+x+') as '+x) or '"'+x+'"', fields_pre) - 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 id IN (%s) AND %s ORDER BY %s' % \ - (','.join(fields_pre2 + ['id']), self._table, - ','.join([str(x) for x in sub_ids]), d1, - self._order), d2) - if not cr.rowcount == len({}.fromkeys(sub_ids)): - raise except_orm(_('AccessError'), - _('You try to bypass an access rule (Document type: %s).') % self._description) - else: - cr.execute('SELECT %s FROM \"%s\" WHERE id IN (%s) ORDER BY %s' % \ - (','.join(fields_pre2 + ['id']), self._table, - ','.join([str(x) for x in sub_ids]), - self._order)) - res.extend(cr.dictfetchall()) - else: - res = map(lambda x: {'id': x}, ids) - - for f in fields_pre: - if self._columns[f].translate: - ids = map(lambda x: x['id'], res) - res_trans = self.pool.get('ir.translation')._get_ids(cr, user, self._name+','+f, 'model', context.get('lang', False) or 'en_US', ids) - for r in res: - r[f] = res_trans.get(r['id'], False) or r[f] - - for table in self._inherits: - col = self._inherits[table] - cols = intersect(self._inherit_fields.keys(), fields) - if not cols: - continue - res2 = self.pool.get(table).read(cr, user, [x[col] for x in res], cols, context, load) - - res3 = {} - for r in res2: - res3[r['id']] = r - del r['id'] - - for record in res: - record.update(res3[record[col]]) - if col not in fields: - del record[col] - - # all fields which need to be post-processed by a simple function (symbol_get) - fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields) - if fields_post: - # maybe it would be faster to iterate on the fields then on res, so that we wouldn't need - # to get the _symbol_get in each occurence - for r in res: - for f in fields_post: - r[f] = self.columns[f]._symbol_get(r[f]) - ids = map(lambda x: x['id'], res) - - # all non inherited fields for which the attribute whose name is in load is False - fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields) - for f in fields_post: - # get the value of that field for all records/ids - res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res) - for record in res: - record[f] = res2[record['id']] - - readonly = None - for vals in res: - for field in vals.copy(): - fobj = None - if field in self._columns: - fobj = self._columns[field] - - if not fobj: - continue - groups = fobj.read - if groups: - edit = False - for group in groups: - module = group.split(".")[0] - grp = group.split(".")[1] - cr.execute("select count(*) from res_groups_users_rel where gid in (select res_id from ir_model_data where name='%s' and module='%s' and model='%s') and uid=%s" % \ - (grp, module, 'res.groups', user)) - readonly = cr.fetchall() - if readonly[0][0] >= 1: - edit = True - break - elif readonly[0][0] == 0: - edit = False - else: - edit = False - - if not edit: - if type(vals[field]) == type([]): - vals[field] = [] - elif type(vals[field]) == type(0.0): - vals[field] = 0 - elif type(vals[field]) == type(''): - vals[field] = '=No Permission=' - else: - vals[field] = False - return res - - def perm_read(self, cr, user, ids, context=None, details=True): - if not context: - context = {} - if not ids: - return [] - fields = '' - if self._log_access: - fields = ', u.create_uid, u.create_date, u.write_uid, u.write_date' - if isinstance(ids, (int, long)): - ids_str = str(ids) - else: - ids_str = ','.join([str(i) for i in ids]) - cr.execute('select u.id'+fields+' from "'+self._table+'" u where u.id in ('+ids_str+')') - res = cr.dictfetchall() + res = super(cacheable_osv, self).read(cr, user, tofetch, nfields, context, load) for r in res: - for key in r: - r[key] = r[key] or False - if key in ('write_uid', 'create_uid', 'uid') and details: - if r[key]: - r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0] - if isinstance(ids, (int, long)): - return res[ids] - return res + self.add((self._name, r['id'], ctx), r) + result.append(filter_dict(r, fields)) - def unlink(self, cr, uid, ids, context=None): - if not context: - context = {} - if not ids: - return True - if isinstance(ids, (int, long)): - ids = [ids] - - fn_list = [] - if self._name in self.pool._store_function.keys(): - list_store = self.pool._store_function[self._name] - fn_data = () - id_change = [] - for tuple_fn in list_store: - for id in ids: - id_change.append(self._store_get_ids(cr, uid, id, tuple_fn, context)[0]) - fn_data = id_change, tuple_fn - fn_list.append(fn_data) - - delta = context.get('read_delta', False) - if delta and self._log_access: - for i in range(0, len(ids), cr.IN_MAX): - sub_ids = ids[i:i+cr.IN_MAX] - cr.execute("select (now() - min(write_date)) <= '%s'::interval " \ - "from \"%s\" where id in (%s)" % - (delta, self._table, ",".join(map(str, sub_ids)))) - res = cr.fetchone() - if res and res[0]: - raise except_orm(_('ConcurrencyException'), - _('This record was modified in the meanwhile')) - - self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink') - - wf_service = netsvc.LocalService("workflow") - for id in ids: - wf_service.trg_delete(uid, self._name, id, cr) - - #cr.execute('select * from '+self._table+' where id in ('+str_d+')', ids) - #res = cr.dictfetchall() - #for key in self._inherits: - # ids2 = [x[self._inherits[key]] for x in res] - # self.pool.get(key).unlink(cr, uid, ids2) - - d1, d2 = self.pool.get('ir.rule').domain_get(cr, uid, self._name) - if d1: - d1 = ' AND '+d1 - - for i in range(0, len(ids), cr.IN_MAX): - sub_ids = ids[i:i+cr.IN_MAX] - ','.join(('%d',)*len(sub_ids)) - if d1: - cr.execute('SELECT id FROM "'+self._table+'" ' \ - 'WHERE id IN ('+str_d+')'+d1, sub_ids+d2) - if not cr.rowcount == len({}.fromkeys(ids)): - raise except_orm(_('AccessError'), - _('You try to bypass an access rule (Document type: %s).') % \ - self._description) - - if d1: - cr.execute('delete from "'+self._table+'" ' \ - 'where id in ('+str_d+')'+d1, sub_ids+d2) - else: - cr.execute('delete from "'+self._table+'" ' \ - 'where id in ('+str_d+')', sub_ids) - if fn_list: - for ids, tuple_fn in fn_list: - self._store_set_values(cr, uid, ids, tuple_fn, id_change, context) - - return True - - # - # TODO: Validate - # - def write(self, cr, user, ids, vals, context=None): - readonly = None - for field in vals.copy(): - fobj = None - if field in self._columns: - fobj = self._columns[field] - else: - fobj = self._inherit_fields[field][2] - if not fobj: - continue - groups = fobj.write - - if groups: - edit = False - for group in groups: - module = group.split(".")[0] - grp = group.split(".")[1] - cr.execute("select count(*) from res_groups_users_rel where gid in (select res_id from ir_model_data where name='%s' and module='%s' and model='%s') and uid=%s" % \ - (grp, module, 'res.groups', user)) - readonly = cr.fetchall() - if readonly[0][0] >= 1: - edit = True - break - elif readonly[0][0] == 0: - edit = False - else: - edit = False - - if not edit: - vals.pop(field) - - if not context: - context = {} - if not ids: - return True - if isinstance(ids, (int, long)): - ids = [ids] - delta = context.get('read_delta', False) - if delta and self._log_access: - for i in range(0, len(ids), cr.IN_MAX): - sub_ids = ids[i:i+cr.IN_MAX] - cr.execute("select (now() - min(write_date)) <= '%s'::interval " \ - "from %s where id in (%s)" % - (delta, self._table, ",".join(map(str, sub_ids)))) - res = cr.fetchone() - if res and res[0]: - for field in vals: - if field in self._columns and self._columns[field]._classic_write: - raise except_orm(_('ConcurrencyException'), - _('This record was modified in the meanwhile')) - - self.pool.get('ir.model.access').check(cr, user, self._name, 'write') - - #for v in self._inherits.values(): - # assert v not in vals, (v, vals) - upd0 = [] - upd1 = [] - upd_todo = [] - updend = [] - direct = [] - totranslate = context.get('lang', False) and (context['lang'] != 'en_US') - for field in vals: - if field in self._columns: - if self._columns[field]._classic_write: - if (not totranslate) or not self._columns[field].translate: - upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0]) - upd1.append(self._columns[field]._symbol_set[1](vals[field])) - direct.append(field) - else: - upd_todo.append(field) - else: - updend.append(field) - if field in self._columns \ - and hasattr(self._columns[field], 'selection') \ - and vals[field]: - if self._columns[field]._type == 'reference': - val = vals[field].split(',')[0] - else: - val = vals[field] - if isinstance(self._columns[field].selection, (tuple, list)): - if val not in dict(self._columns[field].selection): - raise except_orm(_('ValidateError'), - _('The value "%s" for the field "%s" is not in the selection') \ - % (vals[field], field)) - else: - if val not in dict(self._columns[field].selection( - self, cr, user, context=context)): - raise except_orm(_('ValidateError'), - _('The value "%s" for the field "%s" is not in the selection') \ - % (vals[field], field)) - - if self._log_access: - upd0.append('write_uid=%d') - upd0.append('write_date=now()') - upd1.append(user) - - if len(upd0): - - d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name) - if d1: - d1 = ' and '+d1 - - for i in range(0, len(ids), cr.IN_MAX): - sub_ids = ids[i:i+cr.IN_MAX] - ids_str = ','.join(map(str, sub_ids)) - if d1: - cr.execute('SELECT id FROM "'+self._table+'" ' \ - 'WHERE id IN ('+ids_str+')'+d1, d2) - if not cr.rowcount == len({}.fromkeys(sub_ids)): - raise except_orm(_('AccessError'), - _('You try to bypass an access rule (Document type: %s).') % \ - self._description) - else: - cr.execute('SELECT id FROM "'+self._table+'" WHERE id IN ('+ids_str+')') - if not cr.rowcount == len({}.fromkeys(sub_ids)): - raise except_orm(_('AccessError'), - _('You try to write on an record that doesn\'t exist ' \ - '(Document type: %s).') % self._description) - if d1: - cr.execute('update "'+self._table+'" set ' + ','.join(upd0) + ' ' \ - 'where id in ('+ids_str+')'+d1, upd1+ d2) - else: - cr.execute('update "'+self._table+'" set ' + ','.join(upd0) + ' ' \ - 'where id in ('+ids_str+')', upd1) - - if totranslate: - for f in direct: - if self._columns[f].translate: - self.pool.get('ir.translation')._set_ids(cr, user, self._name+','+f, 'model', context['lang'], ids, vals[f]) - - # call the 'set' method of fields which are not classic_write - upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority) - for field in upd_todo: - for id in ids: - self._columns[field].set(cr, self, id, field, vals[field], user, context=context) - - for table in self._inherits: - col = self._inherits[table] - nids = [] - for i in range(0, len(ids), cr.IN_MAX): - sub_ids = ids[i:i+cr.IN_MAX] - ids_str = ','.join(map(str, sub_ids)) - cr.execute('select distinct "'+col+'" from "'+self._table+'" ' \ - 'where id in ('+ids_str+')', upd1) - nids.extend([x[0] for x in cr.fetchall()]) - - v = {} - for val in updend: - if self._inherit_fields[val][0] == table: - v[val] = vals[val] - self.pool.get(table).write(cr, user, nids, v, context) - - self._validate(cr, user, ids, context) - - if 'read_delta' in context: - del context['read_delta'] - - wf_service = netsvc.LocalService("workflow") - for id in ids: - wf_service.trg_write(user, self._name, id, cr) - self._update_function_stored(cr, user, ids, context=context) - - if self._name in self.pool._store_function.keys(): - list_store = self.pool._store_function[self._name] - for tuple_fn in list_store: - flag = False - if not tuple_fn[3]: - flag = True - for field in tuple_fn[3]: - if field in vals.keys(): - flag = True - break - if flag: - id_change = self._store_get_ids(cr, user, ids[0], tuple_fn, context) - self._store_set_values(cr, user, ids[0], tuple_fn, id_change, context) - - return True - - # - # TODO: Should set perm to user.xxx - # - def create(self, cr, user, vals, context=None): - """ create(cr, user, vals, context) -> int - cr = database cursor - user = user id - vals = dictionary of the form {'field_name':field_value, ...} - """ - if not context: - context = {} - self.pool.get('ir.model.access').check(cr, user, self._name, 'create') - - default = [] - - avoid_table = [] - for (t, c) in self._inherits.items(): - if c in vals: - avoid_table.append(t) - for f in self._columns.keys(): # + self._inherit_fields.keys(): - if not f in vals: - default.append(f) - for f in self._inherit_fields.keys(): - if (not f in vals) and (not self._inherit_fields[f][0] in avoid_table): - default.append(f) - - if len(default): - vals.update(self.default_get(cr, user, default, context)) - - tocreate = {} - for v in self._inherits: - if self._inherits[v] not in vals: - tocreate[v] = {} - - (upd0, upd1, upd2) = ('', '', []) - upd_todo = [] - - for v in vals.keys(): - if v in self._inherit_fields: - (table, col, col_detail) = self._inherit_fields[v] - tocreate[table][v] = vals[v] - del vals[v] - - cr.execute("SELECT nextval('"+self._sequence+"')") - id_new = cr.fetchone()[0] - for table in tocreate: - id = self.pool.get(table).create(cr, user, tocreate[table]) - upd0 += ','+self._inherits[table] - upd1 += ',%d' - upd2.append(id) - - for field in vals: - if self._columns[field]._classic_write: - upd0 = upd0 + ',"' + field+'"' - upd1 = upd1 + ',' + self._columns[field]._symbol_set[0] - upd2.append(self._columns[field]._symbol_set[1](vals[field])) - else: - upd_todo.append(field) - if field in self._columns \ - and hasattr(self._columns[field], 'selection') \ - and vals[field]: - if self._columns[field]._type == 'reference': - val = vals[field].split(',')[0] - else: - val = vals[field] - if isinstance(self._columns[field].selection, (tuple, list)): - if val not in dict(self._columns[field].selection): - raise except_orm(_('ValidateError'), - _('The value "%s" for the field "%s" is not in the selection') \ - % (vals[field], field)) - else: - if val not in dict(self._columns[field].selection( - self, cr, user, context=context)): - raise except_orm(_('ValidateError'), - _('The value "%s" for the field "%s" is not in the selection') \ - % (vals[field], field)) - if self._log_access: - upd0 += ',create_uid,create_date' - upd1 += ',%d,now()' - upd2.append(user) - cr.execute('insert into "'+self._table+'" (id'+upd0+") values ("+str(id_new)+upd1+')', tuple(upd2)) - upd_todo.sort(lambda x, y: self._columns[x].priority-self._columns[y].priority) - for field in upd_todo: - self._columns[field].set(cr, self, id_new, field, vals[field], user, context) - - self._validate(cr, user, [id_new], context) - - wf_service = netsvc.LocalService("workflow") - wf_service.trg_create(user, self._name, id_new, cr) - self._update_function_stored(cr, user, [id_new], context=context) - if self._name in self.pool._store_function.keys(): - list_store = self.pool._store_function[self._name] - for tuple_fn in list_store: - id_change = self._store_get_ids(cr, user, id_new, tuple_fn, context) - self._store_set_values(cr, user, id_new, tuple_fn, id_change, context) - - return id_new - - def _store_get_ids(self, cr, uid, ids, tuple_fn, context): - parent_id = getattr(self.pool.get(tuple_fn[0]), tuple_fn[4].func_name)(cr, uid, [ids]) - return parent_id - - def _store_set_values(self, cr, uid, ids, tuple_fn, parent_id, context): - name = tuple_fn[1] - table = tuple_fn[0] - args = {} - vals_tot = getattr(self.pool.get(table), tuple_fn[2])(cr, uid, parent_id, name, args, context) - write_dict = {} - for id in vals_tot.keys(): - write_dict[name] = vals_tot[id] - self.pool.get(table).write(cr, uid, [id], write_dict) - return True - - def _update_function_stored(self, cr, user, ids, context=None): - if not context: - context = {} - f = filter(lambda a: isinstance(self._columns[a], fields.function) \ - and self._columns[a].store, self._columns) - if f: - result = self.read(cr, user, ids, fields=f, context=context) - for res in result: - upd0 = [] - upd1 = [] - for field in res: - if field not in f: - continue - value = res[field] - if self._columns[field]._type in ('many2one', 'one2one'): - value = res[field][0] - upd0.append('"'+field+'"='+self._columns[field]._symbol_set[0]) - upd1.append(self._columns[field]._symbol_set[1](value)) - upd1.append(res['id']) - cr.execute('update "' + self._table + '" set ' + \ - ','.join(upd0) + ' where id = %d', upd1) - return True - - # - # TODO: Validate - # - def perm_write(self, cr, user, ids, fields, context=None): - raise _('This method does not exist anymore') - - # TODO: ameliorer avec NULL - def _where_calc(self, cr, user, args, active_test=True, context=None): - if not context: - context = {} - args = args[:] - # if the object has a field named 'active', filter out all inactive - # records unless they were explicitely asked for - if 'active' in self._columns and (active_test and context.get('active_test', True)): - args = [('&', ('active', '=', 1), tuple(args))] - - if args: - import expression - e = expression.expression(args) - e.parse(cr, user, self, context) - tables = e.get_tables() - qu1, qu2 = e.to_sql() - qu1 = qu1 and [qu1] or [] - else: - qu1, qu2, tables = [], [], ['"%s"' % self._table] - - return (qu1, qu2, tables) - - def _check_qorder(self, word): - if not regex_order.match(word): - raise except_orm(_('AccessError'), _('Bad query.')) - return True - - def search(self, cr, user, args, offset=0, limit=None, order=None, - context=None, count=False): - if not context: - context = {} - # compute the where, order by, limit and offset clauses - (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context) - - if len(qu1): - qu1 = ' where ' + ' and '.join(qu1) - else: - qu1 = '' - - if order: - self._check_qorder(order) - order_by = order or self._order - - limit_str = limit and ' limit %d' % limit or '' - offset_str = offset and ' offset %d' % offset or '' - - - # construct a clause for the rules : - d1, d2 = self.pool.get('ir.rule').domain_get(cr, user, self._name) - if d1: - qu1 = qu1 and qu1+' and '+d1 or ' where '+d1 - qu2 += d2 - - if count: - cr.execute('select count(%s.id) from ' % self._table + - ','.join(tables) +qu1 + limit_str + offset_str, qu2) - res = cr.fetchall() - return res[0][0] - # execute the "main" query to fetch the ids we were searching for - cr.execute('select %s.id from ' % self._table + ','.join(tables) +qu1+' order by '+order_by+limit_str+offset_str, qu2) - res = cr.fetchall() - return [x[0] for x in res] - - # returns the different values ever entered for one field - # this is used, for example, in the client when the user hits enter on - # a char field - def distinct_field_get(self, cr, uid, field, value, args=None, offset=0, limit=None): - if not args: - args = [] - if field in self._inherit_fields: - return self.pool.get(self._inherit_fields[field][0]).distinct_field_get(cr, uid, field, value, args, offset, limit) - else: - return self._columns[field].search(cr, self, args, field, value, offset, limit, uid) - - def name_get(self, cr, user, ids, context=None): - if not context: - context = {} - if not ids: - return [] - if isinstance(ids, (int, long)): - ids = [ids] - return [(r['id'], str(r[self._rec_name])) for r in self.read(cr, user, ids, - [self._rec_name], context, load='_classic_write')] - - def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=80): - if not args: - args = [] - if not context: - context = {} - args = args[:] - if name: - args += [(self._rec_name, operator, name)] - ids = self.search(cr, user, args, limit=limit, context=context) - res = self.name_get(cr, user, ids, context) - return res - - def copy(self, cr, uid, id, default=None, context=None): - if not context: - context = {} - if not default: - default = {} - if 'state' not in default: - if 'state' in self._defaults: - default['state'] = self._defaults['state'](self, cr, uid, context) - data = self.read(cr, uid, [id], context=context)[0] - fields = self.fields_get(cr, uid) + # Appel de fonction si necessaire + tofetch = [] for f in fields: - ftype = fields[f]['type'] + if f not in nfields: + tofetch.append(f) + for f in tofetch: + fvals = self._columns[f].get(cr, self, ids, f, user, context=context) + for r in result: + r[f] = fvals[r['id']] - if self._log_access and f in ('create_date', 'create_uid', 'write_date', 'write_uid'): - del data[f] + # TODO: tri par self._order !! + return result - if f in default: - data[f] = default[f] - elif ftype == 'function': - del data[f] - elif ftype == 'many2one': - try: - data[f] = data[f] and data[f][0] - except: - pass - elif ftype in ('one2many', 'one2one'): - res = [] - rel = self.pool.get(fields[f]['relation']) - for rel_id in data[f]: - # the lines are first duplicated using the wrong (old) - # parent but then are reassigned to the correct one thanks - # to the (4, ...) - res.append((4, rel.copy(cr, uid, rel_id, context=context))) - data[f] = res - elif ftype == 'many2many': - data[f] = [(6, 0, data[f])] - del data['id'] - for v in self._inherits: - del data[self._inherits[v]] - return self.create(cr, uid, data) + def invalidate(self, key): + del self._cache[key[0]][key[1]] - def read_string(self, cr, uid, id, langs, fields=None, context=None): + def write(self, cr, user, ids, values, context=None): if not context: - context = {} - res = {} - res2 = {} - self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'read') - if not fields: - fields = self._columns.keys() + self._inherit_fields.keys() - for lang in langs: - res[lang] = {'code': lang} - for f in fields: - if f in self._columns: - res_trans = self.pool.get('ir.translation')._get_source(cr, uid, self._name+','+f, 'field', lang) - if res_trans: - res[lang][f] = res_trans - else: - res[lang][f] = self._columns[f].string - for table in self._inherits: - cols = intersect(self._inherit_fields.keys(), fields) - res2 = self.pool.get(table).read_string(cr, uid, id, langs, cols, context) - for lang in res2: - if lang in res: - res[lang] = {'code': lang} - for f in res2[lang]: - res[lang][f] = res2[lang][f] - return res + context={} + for id in ids: + self.invalidate((self._name, id)) + return super(cacheable_osv, self).write(cr, user, ids, values, context) - def write_string(self, cr, uid, id, langs, vals, context=None): - if not context: - context = {} - self.pool.get('ir.model.access').check(cr, uid, 'ir.translation', 'write') - for lang in langs: - for field in vals: - if field in self._columns: - self.pool.get('ir.translation')._set_ids(cr, uid, self._name+','+field, 'field', lang, [0], vals[field]) - for table in self._inherits: - cols = intersect(self._inherit_fields.keys(), vals) - if cols: - self.pool.get(table).write_string(cr, uid, id, langs, vals, context) - return True + def unlink(self, cr, user, ids): + self.clear() + return super(cacheable_osv, self).unlink(cr, user, ids) - def check_recursion(self, cr, uid, ids, parent=None): - if not parent: - parent = self._parent_name - ids_parent = ids[:] - while len(ids_parent): - ids_parent2 = [] - for i in range(0, len(ids), cr.IN_MAX): - sub_ids_parent = ids_parent[i:i+cr.IN_MAX] - cr.execute('SELECT distinct "'+parent+'"'+ - ' FROM "'+self._table+'" ' \ - 'WHERE id in ('+','.join(map(str, sub_ids_parent))+')') - ids_parent2.extend(filter(None, map(lambda x: x[0], cr.fetchall()))) - ids_parent = ids_parent2 - for i in ids_parent: - if i in ids: - return False - return True +#cacheable_osv = osv + + +#class FakePool(object): +# def __init__(self, module): +# self.preferred_module = module + +# def get(self, name): +# localpool = module_objects_dict.get(self.preferred_module, {'dict': {}})['dict'] +# if name in localpool: +# obj = localpool[name] +# else: +# obj = pooler.get_pool(cr.dbname).get(name) +# return obj + +# fake_pool = self +# class fake_class(obj.__class__): +# def __init__(self): +# super(fake_class, self).__init__() +# self.pool = fake_pool + +# return fake_class() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From cd06389bcb55890a520d8d1fadffcb0b18e15495 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 13 Aug 2008 12:47:38 +0200 Subject: [PATCH 014/169] Speed Improvement in recursive functions and trees: Implemented Nested Tree algorythm bzr revid: fp@tinyerp.com-20080813104738-q7k1top1y8m9kkiy --- bin/osv/orm.py | 101 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index ffb2fbe16c6..e9a307c73fa 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -284,6 +284,7 @@ class orm_template(object): _defaults = {} _rec_name = 'name' _parent_name = 'parent_id' + _parent_store = False _date_name = 'date' _order = 'id' _sequence = None @@ -344,10 +345,16 @@ class orm_template(object): (('field_'+self._table+'_'+k)[:64], context['module'], 'ir.model.fields', id) ) else: +<<<<<<< TREE + for key,val in vals.items(): + if cols[k][key]<>vals[key]: + cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'],vals['model'],vals['name'])) +======= for key, val in vals.items(): if cols[k][key] != vals[key]: print 'Different', cols[k][key], vals[key], k, key, self._name cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name'])) +>>>>>>> MERGE-SOURCE cr.commit() cr.execute("""UPDATE ir_model_fields SET model_id=%s, field_description=%s, ttype=%s, relate=%s, relation=%s, @@ -1255,8 +1262,27 @@ class orm(orm_template): _log_access = True _table = None - _protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count'] + _protected = ['read','write','create','default_get','perm_read','unlink','fields_get','fields_view_get','search','name_get','distinct_field_get','name_search','copy','import_data','search_count'] + def __parent_store_compute(self, cr): + logger = netsvc.Logger() + logger.notifyChannel('init', netsvc.LOG_INFO, 'Computing parent left and right for table %s...' % (self._table, )) + def browse_rec(root, pos=0): +# TODO: set order + where = self._parent_name+'='+str(root) + if not root: + where = self._parent_name+' IS NULL' + cr.execute('SELECT id FROM '+self._table+' WHERE '+where) + pos2 = pos + 1 + childs = cr.fetchall() + for id in childs: + pos2 = browse_rec(id[0], pos2) + cr.execute('update '+self._table+' set parent_left=%d, parent_right=%d where id=%d', (pos,pos2,root)) + return pos2+1 + browse_rec(None) + return True + def _auto_init(self, cr, context={}): + store_compute = False logger = netsvc.Logger() create = False self._field_create(cr, context=context) @@ -1266,6 +1292,23 @@ class orm(orm_template): cr.execute("CREATE TABLE \"%s\" (id SERIAL NOT NULL, PRIMARY KEY(id)) WITH OIDS" % self._table) create = True cr.commit() + if self._parent_store: + cr.execute("""SELECT c.relname + FROM pg_class c, pg_attribute a + WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid + """, (self._table, 'parent_left')) + if not cr.rowcount: + if 'parent_left' not in self._columns: + logger.notifyChannel('init', netsvc.LOG_ERROR, 'create a column parent_left on object %s: fields.integer(\'Left Parent\', select=1)' % (self._table, )) + if 'parent_right' not in self._columns: + logger.notifyChannel('init', netsvc.LOG_ERROR, 'create a column parent_right on object %s: fields.integer(\'Right Parent\', select=1)' % (self._table, )) + if self._columns[self._parent_name].ondelete<>'cascade': + logger.notifyChannel('init', netsvc.LOG_ERROR, "the columns %s on object must be set as ondelete='cascasde'" % (self._name, self._parent_name)) + cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" INTEGER" % (self._table, 'parent_left')) + cr.execute("ALTER TABLE \"%s\" ADD COLUMN \"%s\" INTEGER" % (self._table, 'parent_right')) + cr.commit() + store_compute = True + if self._log_access: logs = { 'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL', @@ -1475,6 +1518,8 @@ class orm(orm_template): if line2: cr.execute(line2) cr.commit() + if store_compute: + self.__parent_store_compute(cr) def __init__(self, cr): super(orm, self).__init__(cr) @@ -2012,6 +2057,48 @@ class orm(orm_template): self.pool.get(table).write(cr, user, nids, v, context) self._validate(cr, user, ids, context) +# TODO: use _order to set dest at the right position and not first node of parent + if self._parent_store and self._parent_name in vals: + cr.execute('select parent_left,parent_right from '+self._table+' where id=%d', (vals[self._parent_name],)) + res = cr.fetchone() + if res: + pleft,pright = res + else: + cr.execute('select max(parent_right),max(parent_right)+1 from '+self._table) + pleft,pright = cr.fetchone() + cr.execute('select parent_left,parent_right,id from '+self._table+' where id in ('+','.join(map(lambda x:'%d',ids))+')', ids) + dest = pleft + 1 + for cleft,cright,cid in cr.fetchall(): + if cleft > pleft: + treeshift = pleft - cleft + 1 + leftbound = pleft+1 + rightbound = cleft-1 + cwidth = cright-cleft+1 + leftrange = cright + rightrange = pleft + else: + treeshift = pleft - cright + leftbound = cright + 1 + rightbound = pleft + cwidth = cleft-cright-1 + leftrange = pleft+1 + rightrange = cleft + cr.execute('UPDATE '+self._table+''' + SET + parent_left = CASE + WHEN parent_left BETWEEN %d AND %d THEN parent_left + %d + WHEN parent_left BETWEEN %d AND %d THEN parent_left + %d + ELSE parent_left + END, + parent_right = CASE + WHEN parent_right BETWEEN %d AND %d THEN parent_right + %d + WHEN parent_right BETWEEN %d AND %d THEN parent_right + %d + ELSE parent_right + END + WHERE + parent_left<%d OR parent_right>%d; + ''', (leftbound,rightbound,cwidth,cleft,cright,treeshift,leftbound,rightbound, + cwidth,cleft,cright,treeshift,leftrange,rightrange)) if 'read_delta' in context: del context['read_delta'] @@ -2124,6 +2211,18 @@ class orm(orm_template): self._validate(cr, user, [id_new], context) + if self._parent_store: + parent = vals.get(self._parent_name, False) + if parent: + cr.execute('select parent_left from '+self._table+' where id=%d', (parent,)) + pleft = cr.fetchone()[0] + else: + cr.execute('select max(parent_right) from '+self._table) + pleft = cr.fetchone()[0] + cr.execute('update '+self._table+' set parent_left=%d,parent_right=%d', (pleft+1,pleft+2)) + cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%d', (pleft,)) + cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%d', (pleft,)) + wf_service = netsvc.LocalService("workflow") wf_service.trg_create(user, self._name, id_new, cr) self._update_function_stored(cr, user, [id_new], context=context) From b1cbe8d9fd91b5f8dbf3622048ccf2707e6d66df Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 13 Aug 2008 12:50:07 +0200 Subject: [PATCH 015/169] Nested Tree for Chart of Accounts bzr revid: fp@tinyerp.com-20080813105007-14n46fcwluokbgih --- addons/account/account.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/addons/account/account.py b/addons/account/account.py index 1797563874a..aa22b940528 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -132,6 +132,7 @@ class account_account(osv.osv): _order = "code" _name = "account.account" _description = "Account" + _parent_store = True def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): @@ -267,7 +268,7 @@ class account_account(osv.osv): 'code': fields.char('Code', size=64), 'type': fields.selection(_code_get, 'Account Type', required=True), # 'parent_id': fields.many2many('account.account', 'account_account_rel', 'child_id', 'parent_id', 'Parents'), - 'parent_id': fields.many2one('account.account','Parent'), + 'parent_id': fields.many2one('account.account','Parent', ondelete='cascade'), 'child_parent_ids':fields.one2many('account.account','parent_id','Children'), 'child_consol_ids':fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children',domain=[('type','=','root'), ('type', '=', 'consolidation')]), 'child_id': fields.function(_get_child_ids, method=True, type='many2many',relation="account.account",string="Children Accounts"), @@ -285,6 +286,9 @@ class account_account(osv.osv): 'company_currency_id': fields.function(_get_company_currency, method=True, type='many2one', relation='res.currency', string='Company Currency'), 'company_id': fields.many2one('res.company', 'Company', required=True), 'active': fields.boolean('Active', select=2), + + 'parent_left': fields.integer('Parent Left', select=1), + 'parent_right': fields.integer('Parent Right', select=1), } def _default_company(self, cr, uid, context={}): From 3cf0eebe87775b2ea8a8979b69ea575616cf1bf7 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Wed, 13 Aug 2008 15:52:16 +0200 Subject: [PATCH 016/169] fix merge error bzr revid: christophe@tinyerp.com-20080813135216-4dfw23y6jvb60tjh --- bin/osv/orm.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index e9a307c73fa..cd42f265128 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -345,16 +345,10 @@ class orm_template(object): (('field_'+self._table+'_'+k)[:64], context['module'], 'ir.model.fields', id) ) else: -<<<<<<< TREE - for key,val in vals.items(): - if cols[k][key]<>vals[key]: - cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'],vals['model'],vals['name'])) -======= for key, val in vals.items(): if cols[k][key] != vals[key]: - print 'Different', cols[k][key], vals[key], k, key, self._name + #print 'Different', cols[k][key], vals[key], k, key, self._name cr.execute('update ir_model_fields set field_description=%s where model=%s and name=%s', (vals['field_description'], vals['model'], vals['name'])) ->>>>>>> MERGE-SOURCE cr.commit() cr.execute("""UPDATE ir_model_fields SET model_id=%s, field_description=%s, ttype=%s, relate=%s, relation=%s, From 920d70c47b7fd09951c78665927bf6ee36dbbabd Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 13 Aug 2008 16:26:52 +0200 Subject: [PATCH 017/169] Improvement: Make nvoice in sales order and lines not visible in form view bzr revid: fp@tinyerp.com-20080813142652-ipqlhvc8sy2xwvg8 --- addons/sale/sale_wizard.xml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/addons/sale/sale_wizard.xml b/addons/sale/sale_wizard.xml index 6a0d388e75f..ece746bfa12 100644 --- a/addons/sale/sale_wizard.xml +++ b/addons/sale/sale_wizard.xml @@ -2,9 +2,21 @@ - + - + - \ No newline at end of file + From a2772da7ef0c0fc64b19addf80f2ac85423e84f3 Mon Sep 17 00:00:00 2001 From: "mga@tinyerp.com" <> Date: Thu, 14 Aug 2008 11:30:47 +0530 Subject: [PATCH 018/169] * Change View for the Server Action * implement action for the workflow * remove extended from the partner property bzr revid: mga@tinyerp.com-20080814060047-e0e3mdsx3vuvpth9 --- bin/addons/base/ir/ir.xml | 20 +++++++---- bin/addons/base/ir/ir_actions.py | 36 +++++++++++++++++--- bin/addons/base/ir/ir_ui_view.py | 2 +- bin/addons/base/res/partner/partner_view.xml | 2 +- 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/bin/addons/base/ir/ir.xml b/bin/addons/base/ir/ir.xml index 964ea69b676..d68c126bf60 100644 --- a/bin/addons/base/ir/ir.xml +++ b/bin/addons/base/ir/ir.xml @@ -1043,13 +1043,19 @@ - - - - - - - + + + + + + + + + + + + + diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index 46386af1383..c31b33de6fc 100644 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -255,6 +255,25 @@ class act_url(osv.osv): } act_url() +def _get_fields(self, cr, uid, context={}): + + return [('key','Email Value')] + +def model_get(self, cr, uid, context={}): + wkf_pool = self.pool.get('workflow') + ids = wkf_pool.search(cr, uid, []) + osvs = wkf_pool.read(cr, uid, ids, ['osv']) + + res = [] + mpool = self.pool.get('ir.model') + for osv in osvs: + model = osv.get('osv') + id = mpool.search(cr, uid, [('model','=',model)]) + name = mpool.read(cr, uid, id)[0]['name'] + res.append((model, name)) + + return res + # # Actions that are run on the server side # @@ -274,15 +293,16 @@ class actions_server(osv.osv): ('object_write','Write Object'), ('client_action','Client Action'), ('other','Others Actions'), - ], 'Action State', required=True, size=32), + ], 'Action State', required=True, size=32, change_default=True), 'code': fields.text('Python Code'), 'sequence': fields.integer('Sequence'), 'model_id': fields.many2one('ir.model', 'Model', required=True), 'trigger_name': fields.char('Trigger Name', size=128), - 'trigger_object': fields.char('Trigger Object', size=128), - 'trigger_object_id': fields.char('Trigger Object ID', size=128), + 'trigger_obj_id': fields.reference('Trigger On', selection=model_get, size=128), + #'trigger_object': fields.char('Trigger Object', size=128), + #'trigger_object_id': fields.char('Trigger Object ID', size=128), 'message': fields.text('Message', translate=True), - 'address': fields.char('Email Address', size=128), + 'address': fields.selection(_get_fields ,'Email / SMS', size=128), 'child_ids': fields.one2many('ir.actions.actions', 'parent_id', 'Others Actions'), 'usage': fields.char('Action Usage', size=32), 'type': fields.char('Report Type', size=32, required=True), @@ -301,6 +321,7 @@ class actions_server(osv.osv): # If you plan to return an action, assign: action = {...} """ } + # # Context should contains: # ids : original ids @@ -331,11 +352,18 @@ class actions_server(osv.osv): logger.notifyChannel('email', netsvc.LOG_INFO, 'Email successfully send to : %s' % (action.address)) else: logger.notifyChannel('email', netsvc.LOG_ERROR, 'Failed to send email to : %s' % (action.address)) + if action.state == 'trigger': + #wf_service = netsvc.LocalService("workflow") + print action.trigger_obj_id + #wf_service.trg_validate(uid, ) if action.state == 'sms': if tools.sms_send(user, password, api_id, text, to) == True: logger.notifyChannel('sms', netsvc.LOG_INFO, 'SMS successfully send to : %s' % (action.address)) else: logger.notifyChannel('sms', netsvc.LOG_ERROR, 'Failed to send SMS to : %s' % (action.address)) + if action.state == 'other': + print 'Actions to be Execute', action.child_ids + return False actions_server() diff --git a/bin/addons/base/ir/ir_ui_view.py b/bin/addons/base/ir/ir_ui_view.py index f015478a568..7066d429bd4 100644 --- a/bin/addons/base/ir/ir_ui_view.py +++ b/bin/addons/base/ir/ir_ui_view.py @@ -116,7 +116,7 @@ class view(osv.osv): return result - return super(view, self).write(cr, uid, ids, vals, context) + return super(view, self).write(cr, uid, ids, vals, context) view() class view_sc(osv.osv): diff --git a/bin/addons/base/res/partner/partner_view.xml b/bin/addons/base/res/partner/partner_view.xml index ba485352921..8f16f953615 100644 --- a/bin/addons/base/res/partner/partner_view.xml +++ b/bin/addons/base/res/partner/partner_view.xml @@ -279,7 +279,7 @@ - + From ec63f82ad1e0e0ab486fb1b781c0d5b2fdfa06d8 Mon Sep 17 00:00:00 2001 From: "mga@tinyerp.com" <> Date: Thu, 14 Aug 2008 14:10:46 +0530 Subject: [PATCH 019/169] small change in other Actions in Server Action bzr revid: mga@tinyerp.com-20080814084046-w8lxbircglwu7ddw --- bin/addons/base/ir/ir_actions.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) mode change 100644 => 100755 bin/addons/base/ir/ir_actions.py diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py old mode 100644 new mode 100755 index c31b33de6fc..159707cdd1d --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -352,17 +352,39 @@ class actions_server(osv.osv): logger.notifyChannel('email', netsvc.LOG_INFO, 'Email successfully send to : %s' % (action.address)) else: logger.notifyChannel('email', netsvc.LOG_ERROR, 'Failed to send email to : %s' % (action.address)) + if action.state == 'trigger': - #wf_service = netsvc.LocalService("workflow") - print action.trigger_obj_id - #wf_service.trg_validate(uid, ) + wf_service = netsvc.LocalService("workflow") + res = str(action.trigger_obj_id).split(',') + model = res[0] + id = res[1] + wf_service.trg_validate(uid, model, int(id), action.trigger_name, cr) + if action.state == 'sms': if tools.sms_send(user, password, api_id, text, to) == True: logger.notifyChannel('sms', netsvc.LOG_INFO, 'SMS successfully send to : %s' % (action.address)) else: logger.notifyChannel('sms', netsvc.LOG_ERROR, 'Failed to send SMS to : %s' % (action.address)) if action.state == 'other': - print 'Actions to be Execute', action.child_ids + localdict = { + 'self': self.pool.get(action.model_id.model), + 'context': context, + 'time': time, + 'ids': ids, + 'cr': cr, + 'uid': uid + } + + for act in action.child_ids: + code = """ + action = { + 'res_model': %s, + 'type': %s, + 'name': %s, + 'usage': %s, + } + """ % (action.model_id.model, act.type, act.name, act.usage) + exec action.code in localdict return False actions_server() From e6aeef6e28f0252e7e6fd5dd876c427251268e94 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 14 Aug 2008 11:51:20 +0200 Subject: [PATCH 020/169] add reverse_enumerate method bzr revid: christophe@tinyerp.com-20080814095120-q474vbn5qc03clp1 --- bin/tools/misc.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/bin/tools/misc.py b/bin/tools/misc.py index 3c6d726a84e..d905be92d42 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -48,6 +48,8 @@ if sys.version_info[:2] < (2, 4): else: from threading import local +from itertools import izip + # initialize a database with base/base.sql def init_db(cr): import addons @@ -288,7 +290,22 @@ def flatten(list): r.append(e) return r - +reverse_enumerate = lambda l: izip(xrange(len(l)-1, -1, -1), reversed(l)) +reverse_enumerate.__doc__ = \ +"""Like enumerate but in the other sens +>>> a = ['a', 'b', 'c'] +>>> it = reverse_enumerate(a) +>>> it.next() +(2, 'c') +>>> it.next() +(1, 'b') +>>> it.next() +(0, 'a') +>>> it.next() +Traceback (most recent call last): + File "", line 1, in +StopIteration +""" #---------------------------------------------------------- # Emails From 4944ae6cdb8e56b96a99d0a4489f7fc8c26f6e4d Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 14 Aug 2008 14:04:41 +0200 Subject: [PATCH 021/169] modify the expression parser to use a (more) real polish notation. bzr revid: christophe@tinyerp.com-20080814120441-wv4tmqj06ze725ph --- bin/osv/expression.py | 414 ++++++++++++++++++------------------------ bin/osv/orm.py | 5 +- 2 files changed, 180 insertions(+), 239 deletions(-) diff --git a/bin/osv/expression.py b/bin/osv/expression.py index ee5b3c8a39c..f3a15450ace 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -1,67 +1,28 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -from tools import flatten +from tools import flatten, reverse_enumerate class expression(object): """ parse a domain expression - examples: - - >>> e = [('foo', '=', 'bar')] - >>> expression(e).parse().to_sql() - 'foo = bar' - >>> e = [('id', 'in', [1,2,3])] - >>> expression(e).parse().to_sql() - 'id in (1, 2, 3)' - >>> e = [('field', '=', 'value'), ('field', '<>', 'value')] - >>> expression(e).parse().to_sql() - '( field = value AND field <> value )' - >>> e = [('&', ('field', '<', 'value'), ('field', '>', 'value'))] - >>> expression(e).parse().to_sql() - '( field < value AND field > value )' - >>> e = [('|', ('field', '=', 'value'), ('field', '=', 'value'))] - >>> expression(e).parse().to_sql() - '( field = value OR field = value )' - >>> e = [('&', ('field1', '=', 'value'), ('field2', '=', 'value'), ('|', ('field3', '<>', 'value'), ('field4', '=', 'value')))] - >>> expression(e).parse().to_sql() - '( field1 = value AND field2 = value AND ( field3 <> value OR field4 = value ) )' - >>> e = [('&', ('|', ('a', '=', '1'), ('b', '=', '2')), ('|', ('c', '=', '3'), ('d', '=', '4')))] - >>> expression(e).parse().to_sql() - '( ( a = 1 OR b = 2 ) AND ( c = 3 OR d = 4 ) )' - >>> e = [('|', (('a', '=', '1'), ('b', '=', '2')), (('c', '=', '3'), ('d', '=', '4')))] - >>> expression(e).parse().to_sql() - '( ( a = 1 AND b = 2 ) OR ( c = 3 AND d = 4 ) )' - >>> expression(e).parse().get_tables() - [] - >>> expression('fail').parse().to_sql() - Traceback (most recent call last): - ... - ValueError: Bad expression: 'fail' - >>> e = [('fail', 'is', 'True')] - >>> expression(e).parse().to_sql() - Traceback (most recent call last): - ... - ValueError: Bad expression: ('&', ('fail', 'is', 'True')) + use a real polish notation + leafs are still in a ('foo', '=', 'bar') format + For more info: http://... """ def _is_operator(self, element): return isinstance(element, str) \ - and element in ['&', '|'] + and element in ['&', '|', '!'] def _is_leaf(self, element): return isinstance(element, tuple) \ and len(element) == 3 \ and element[1] in ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of') - def _is_expression(self, element): - return isinstance(element, tuple) \ - and len(element) > 2 \ - and self._is_operator(element[0]) - - def __execute_recursive_in(self, cr, s, f, w, ids): + #deprecated -> use _left and _right... res = [] for i in range(0, len(ids), cr.IN_MAX): subids = ids[i:i+cr.IN_MAX] @@ -74,41 +35,21 @@ class expression(object): def __init__(self, exp): - if exp and isinstance(exp, tuple): - if not self._is_leaf(exp) and not self._is_operator(exp[0]): - exp = list(exp) - if exp and isinstance(exp, list): - if len(exp) == 1 and self._is_leaf(exp[0]): - exp = exp[0] - else: - if len(exp) == 3 and self._is_leaf(tuple(exp)): - exp=[exp] - if not self._is_operator(exp[0][0]): - if isinstance(exp[0],list): - exp=tuple(exp[0]) - else: - exp.insert(0, '&') - exp = tuple(exp) - else: - exp = exp[0] + + # check if the expression is valid + if not reduce(lambda acc, val: acc and (self._is_operator(val) or self._is_leaf(val)), exp, True): + raise ValueError('Bad expression: %r' % (exp,)) self.__exp = exp - self.__operator = '&' - self.__children = [] - - self.__tables = [] + self.__tables = {} # used to store the table to use for the sql generation. key = index of the leaf self.__joins = [] - self.__table = None + self.__main_table = None # 'root' table. set by parse() + + self.__DUMMY_LEAF = (1, '=', 1) # a dummy leaf that must not be parsed or sql generated - self.__left, self.__right = None, None - if self._is_leaf(self.__exp): - self.__left, self.__operator, self.__right = self.__exp - if isinstance(self.__right, list): - self.__right = tuple(self.__right) - elif exp and not self._is_expression(self.__exp): - raise ValueError('Bad expression: %r' % (self.__exp,)) def parse(self, cr, uid, table, context): + """ transform the leafs of the expression """ def _rec_get(ids, table, parent): if not ids: @@ -119,228 +60,225 @@ class expression(object): if not self.__exp: return self - if self._is_leaf(self.__exp): - self.__table = table - self.__tables.append(self.__table._table) - if self.__left in table._inherit_fields: - self.__table = table.pool.get(table._inherit_fields[self.__left][0]) - if self.__table._table not in self.__tables: - self.__tables.append(self.__table._table) - self.__joins.append('%s.%s' % (table._table, table._inherits[self.__table._name])) - fargs = self.__left.split('.', 1) - field = self.__table._columns.get(fargs[0], False) + self.__main_table = table + working_table = table + + for i, e in enumerate(self.__exp): + if self._is_operator(e) or e == self.__DUMMY_LEAF: + continue + left, operator, right = e + + if left in table._inherit_fields: + working_table = table.pool.get(table._inherit_fields[self.__left][0]) + if working_table not in self.__tables.values(): + self.__joins.append('%s.%s' % (table._table, table._inherits[working_table._name])) + + self.__tables[i] = working_table + + fargs = left.split('.', 1) + field = working_table._columns.get(fargs[0], False) if not field: - if self.__left == 'id' and self.__operator == 'child_of': - self.__right += _rec_get(self.__right, self.__table, self.__table._parent_name) - self.__operator = 'in' - return self + if left == 'id' and operator == 'child_of': + right += _rec_get(right, working_table, working_table._parent_name) + self.__exp[i] = ('id', 'in', right) + continue + + field_obj = table.pool.get(field._obj) if len(fargs) > 1: if field._type == 'many2one': - self.__left = fargs[0] - self.__right = table.pool.get(field._obj).search(cr, uid, [(fargs[1], self.__operator, self.__right)], context=context) - self.__operator = 'in' - return self + right = field_obj.search(cr, uid, [(fargs[1], operator, right)], context=context) + self.__exp[i] = (fargs[0], 'in', right) + continue - field_obj = table.pool.get(field._obj) if field._properties: # this is a function field - if not field._fnct_search and not field.store: - # the function field doesn't provide a search function and doesn't store values in the database, so we must ignore it : we generate a dummy leaf - self.__left, self.__operator, self.__right = 1, '=', 1 - self.__exp = '' # force to generate an empty sql expression - else: - # we need to replace this leaf to a '&' expression - # we clone ourself... - import copy - newexp = copy.copy(self) - self.__table = None - self.__tables, self.__joins = [], [] - self.__children = [] + if not field.store: + if not field._fnct_search: + # the function field doesn't provide a search function and doesn't store + # 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]]) + # 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 - if field._fnct_search: - subexp = field.search(cr, uid, table, self.__left, [self.__exp]) - self.__children.append(expression(subexp).parse(cr, uid, table, context)) - if field.store: - self.__children.append(newexp) - - self.__left, self.__right = None, None - self.__operator = '&' - self.__exp = ('&',) + tuple([tuple(e.__exp) for e in self.__children]) elif field._type == 'one2many': - if isinstance(self.__right, basestring): - ids2 = [x[0] for x in field_obj.name_search(cr, uid, self.__right, [], self.__operator)] + if isinstance(right, basestring): + ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator)] else: - ids2 = self.__right + ids2 = list(right) if not ids2: - self.__left, self.__operator, self.__right = 'id', '=', '0' + self.__exp[i] = ('id', '=', '0') else: - self.__left, self.__operator, self.__right = 'id', 'in', self.__execute_recursive_in(cr, field._fields_id, field_obj._table, 'id', ids2) + self.__exp[i] = ('id', 'in', self.__execute_recursive_in(cr, field._fields_id, field_obj._table, 'id', ids2)) elif field._type == 'many2many': #FIXME - if self.__operator == 'child_of': - if isinstance(self.__right, basestring): - ids2 = [x[0] for x in field_obj.name_search(cr, uid, self.__right, [], 'like')] + if operator == 'child_of': + if isinstance(right, basestring): + ids2 = [x[0] for x in field_obj.name_search(cr, uid, right, [], 'like')] else: - ids2 = self.__right + ids2 = list(right) def _rec_convert(ids): if field_obj == table: return ids return self.__execute_recursive_in(cr, field._id1, field._rel, field._id2, ids) - self.__left, self.__operator, self.__right = 'id', 'in', _rec_convert(ids2 + _rec_get(ids2, field_obj, self.__table._parent_name)) + self.__exp[i] = ('id', 'in', _rec_convert(ids2 + _rec_get(ids2, field_obj, working_table._parent_name))) else: - if isinstance(self.__right, basestring): - res_ids = [x[0] for x in field_obj.name_search(cr, uid, self.__right, [], self.__operator)] + if isinstance(right, basestring): + res_ids = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator)] else: - res_ids = self.__right - self.__left, self.__operator, self.__right = 'id', 'in', self.__execute_recursive_in(cr, field._id1, field._rel, field._id2, res_ids) or [0] + res_ids = list(right) + self.__exp[i] = ('id', 'in', self.__execute_recursive_in(cr, field._id1, field._rel, field._id2, res_ids) or [0]) elif field._type == 'many2one': - if self.__operator == 'child_of': - if isinstance(self.__right, basestring): - ids2 = [x[0] for x in field_obj.search_name(cr, uid, self.__right, [], 'like')] + if operator == 'child_of': + if isinstance(right, basestring): + ids2 = [x[0] for x in field_obj.search_name(cr, uid, right, [], 'like')] else: - ids2 = list(self.__right) + ids2 = list(right) self.__operator = 'in' - if field._obj != self.__table._name: - self.__right = ids2 + _rec_get(ids2, field_obj, self.__table._parent_name) + if field._obj != working_table._name: + right = ids2 + _rec_get(ids2, field_obj, working_table._parent_name) else: - self.__right = ids2 + _rec_get(ids2, self.__table, self.__left) - self.__left = 'id' + right = ids2 + _rec_get(ids2, working_table, left) + left = 'id' + self.__exp[i] = (left, 'in', right) else: - if isinstance(self.__right, basestring): - res_ids = field_obj.name_search(cr, uid, self.__right, [], self.__operator) - self.__operator = 'in' - self.__right = map(lambda x: x[0], res_ids) + if isinstance(right, basestring): + res_ids = field_obj.name_search(cr, uid, right, [], operator) + right = map(lambda x: x[0], res_ids) + self.__exp[i] = (left, 'in', right) else: # other field type if field.translate: - if self.__operator in ('like', 'ilike', 'not like', 'not ilike'): - self.__right = '%%%s%%' % self.__right + if operator in ('like', 'ilike', 'not like', 'not ilike'): + right = '%%%s%%' % right query1 = '( SELECT res_id' \ ' FROM ir_translation' \ ' WHERE name = %s' \ ' AND lang = %s' \ ' AND type = %s' \ - ' AND value ' + self.__operator + ' %s' \ + ' AND value ' + operator + ' %s' \ ') UNION (' \ ' SELECT id' \ - ' FROM "' + self.__table._table + '"' \ - ' WHERE "' + self.__left + '" ' + self.__operator + ' %s' \ + ' FROM "' + working_table._table + '"' \ + ' WHERE "' + left + '" ' + operator + ' %s' \ ')' - query2 = [self.__table._name + ',' + self.__left, + query2 = [working_table._name + ',' + left, context.get('lang', False) or 'en_US', 'model', - self.__right, - self.__right, + right, + right, ] - self.__left = 'id' - self.__operator = 'inselect' - self.__right = (query1, query2,) + self.__exp[i] = ('id', 'inselect', (query1, query2)) - - elif self._is_expression(self.__exp): - self.__operator = self.__exp[0] - - for element in self.__exp[1:]: - if not self._is_operator(element): - self.__children.append(expression(element).parse(cr, uid, table, context)) return self - def to_sql(self): - if not self.__exp: - return ('', []) - elif self._is_leaf(self.__exp): - if self.__operator == 'inselect': - query = '(%s.%s in (%s))' % (self.__table._table, self.__left, self.__right[0]) - params = self.__right[1] - elif self.__operator in ['in', 'not in']: - params = self.__right[:] - len_before = len(params) - for i in range(len_before)[::-1]: - if params[i] == False: - del params[i] + def __leaf_to_sql(self, leaf, table): + left, operator, right = leaf - len_after = len(params) - check_nulls = len_after != len_before - query = '(1=0)' + if operator == 'inselect': + query = '(%s.%s in (%s))' % (table._table, left, right[0]) + params = right[1] + elif operator in ['in', 'not in']: + params = right[:] + len_before = len(params) + for i in range(len_before)[::-1]: + if params[i] == False: + del params[i] - if len_after: - if self.__left == 'id': - instr = ','.join(['%d'] * len_after) - else: - instr = ','.join([self.__table._columns[self.__left]._symbol_set[0]] * len_after) + len_after = len(params) + check_nulls = len_after != len_before + query = '(1=0)' - query = '(%s.%s %s (%s))' % (self.__table._table, self.__left, self.__operator, instr) - - if check_nulls: - query = '(%s OR %s IS NULL)' % (query, self.__left) - else: - params = [] - if self.__right is False and self.__operator == '=': - query = '%s IS NULL' % self.__left - elif self.__right is False and self.__operator == '<>': - query = '%s IS NOT NULL' % self.__left + if len_after: + if left == 'id': + instr = ','.join(['%d'] * len_after) else: - if self.__left == 'id': - query = '%s.id %s %%s' % (self.__table._table, self.__operator) - params = self.__right - else: - like = self.__operator in ('like', 'ilike', 'not like', 'not ilike') - - op = self.__operator == '=like' and 'like' or self.__operator - if self.__left in self.__table._columns: - format = like and '%s' or self.__table._columns[self.__left]._symbol_set[0] - query = '(%s.%s %s %s)' % (self.__table._table, self.__left, op, format) - else: - query = "(%s.%s %s '%s')" % (self.__table._table, self.__left, op, self.__right) - - add_null = False - if like: - if isinstance(self.__right, str): - str_utf8 = self.__right - elif isinstance(self.__right, unicode): - str_utf8 = self.__right.encode('utf-8') - else: - str_utf8 = str(self.__right) - params = '%%%s%%' % str_utf8 - add_null = not str_utf8 - elif self.__left in self.__table._columns: - params = self.__table._columns[self.__left]._symbol_set[1](self.__right) - - if add_null: - query = '(%s OR %s IS NULL)' % (query, self.__left) - - joins = ' AND '.join(map(lambda j: '%s.id = %s' % (self.__table._table, j), self.__joins)) - if joins: - query = '(%s AND (%s))' % (joins, query) - if isinstance(params, basestring): - params = [params] - return (query, params) + instr = ','.join([table._columns[left]._symbol_set[0]] * len_after) + query = '(%s.%s %s (%s))' % (table._table, left, operator, instr) + if check_nulls: + query = '(%s OR %s IS NULL)' % (query, left) else: - children = [child.to_sql() for child in self.__children] - params = flatten([child[1] for child in children]) - query = "( %s )" % (" %s " % {'&': 'AND', '|': 'OR'}[self.__operator]).join([child[0] for child in children if child[0]]) - return (query, params) + params = [] + if right is False and operator == '=': + query = '%s IS NULL' % left + elif right is False and operator in ['<>', '!=']: + query = '%s IS NOT NULL' % left + else: + if left == 'id': + query = '%s.id %s %%s' % (table._table, operator) + params = right + else: + like = operator in ('like', 'ilike', 'not like', 'not ilike') - def __get_tables(self): - return self.__tables + [child.__get_tables() for child in self.__children] + op = operator == '=like' and 'like' or operator + if left in table._columns: + format = like and '%s' or table._columns[left]._symbol_set[0] + query = '(%s.%s %s %s)' % (table._table, left, op, format) + else: + query = "(%s.%s %s '%s')" % (table._table, left, op, right) + + add_null = False + if like: + if isinstance(right, str): + str_utf8 = right + elif isinstance(right, unicode): + str_utf8 = right.encode('utf-8') + else: + str_utf8 = str(right) + params = '%%%s%%' % str_utf8 + add_null = not str_utf8 + elif left in table._columns: + params = table._columns[left]._symbol_set[1](right) + + if add_null: + query = '(%s OR %s IS NULL)' % (query, left) + + if isinstance(params, basestring): + params = [params] + return (query, params) + + + def to_sql(self): + stack = [] + params = [] + for i, e in reverse_enumerate(self.__exp): + if self._is_leaf(e): + table = self.__tables.has_key(i) and self.__tables[i] or self.__main_table + q, p = self.__leaf_to_sql(e, table) + params.insert(0, p) + stack.append(q) + else: + if e == '!': + stack.append('(NOT (%s))' % (stack.pop(),)) + else: + ops = {'&': ' AND ', '|': ' OR '} + q1 = stack.pop() + q2 = stack.pop() + stack.append('(%s %s %s)' % (q1, ops[e], q2,)) + + query = ' AND '.join(reversed(stack)) + joins = ' AND '.join(map(lambda j: '%s.id = %s' % (self.__main_table._table, j), self.__joins)) + if joins: + query = '(%s AND (%s))' % (joins, query) + return (query, flatten(params)) def get_tables(self): - return ['"%s"' % t for t in set(flatten(self.__get_tables()))] - - #def - -if __name__ == '__main__': - pass - #import doctest - #doctest.testmod() + return ['"%s"' % t._table for t in set(self.__tables.values())] # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/osv/orm.py b/bin/osv/orm.py index cd42f265128..ddf985b7601 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -2280,7 +2280,10 @@ class orm(orm_template): # if the object has a field named 'active', filter out all inactive # records unless they were explicitely asked for if 'active' in self._columns and (active_test and context.get('active_test', True)): - args = [('&', ('active', '=', 1), tuple(args))] + if args: + args = ['&', ('active', '=', 1)] + args + else: + args = [('active', '=', 1)] if args: import expression From f3108d24716de4f79ff02ca4b6fa8198c84eaa72 Mon Sep 17 00:00:00 2001 From: Jay Vora Date: Thu, 14 Aug 2008 18:15:58 +0530 Subject: [PATCH 022/169] Minor change:Covered Root account adding possibility on nested tree algorithm for account.account. bzr revid: jvo@tinyerp.com-20080814124558-p6fp6fdrrp0l6qzp --- bin/osv/orm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index cd42f265128..717f4ac9b4b 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -2212,7 +2212,7 @@ class orm(orm_template): pleft = cr.fetchone()[0] else: cr.execute('select max(parent_right) from '+self._table) - pleft = cr.fetchone()[0] + pleft = cr.fetchone()[0] or 0 cr.execute('update '+self._table+' set parent_left=%d,parent_right=%d', (pleft+1,pleft+2)) cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%d', (pleft,)) cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%d', (pleft,)) From da09f0214048368af79d32fc5696965744e3fdb3 Mon Sep 17 00:00:00 2001 From: Jay Vora Date: Thu, 14 Aug 2008 19:07:58 +0530 Subject: [PATCH 023/169] On account move line:Added tax and analytic account fields.modified create and write method on account move line creation bzr revid: jvo@tinyerp.com-20080814133758-z76lxtj88yqm7hlc --- addons/account/account.py | 2 +- addons/account/account_move_line.py | 76 +++++++++++++++++++++-- addons/account/account_view.xml | 10 ++++ addons/account/data/account_data2.xml | 86 +++++++++++++++++++-------- 4 files changed, 143 insertions(+), 31 deletions(-) diff --git a/addons/account/account.py b/addons/account/account.py index 1797563874a..61d38f3cd68 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -1127,7 +1127,7 @@ class account_tax(osv.osv): 'ref_tax_sign': fields.float('Tax Code Sign', help="Usualy 1 or -1."), 'include_base_amount': fields.boolean('Include in base amount', help="Indicate if the amount of tax must be included in the base amount for the computation of the next taxes"), 'company_id': fields.many2one('res.company', 'Company', required=True), - 'description': fields.char('Internal Name', 32), + 'description': fields.char('Internal Name',size=32), } def name_get(self, cr, uid, ids, context={}): diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index b93afede487..795bae5fb83 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -49,9 +49,9 @@ class account_move_line(osv.osv): if state: if state.lower() not in ['all']: where_move_state= " AND "+obj+".move_id in (select id from account_move where account_move.state = '"+state+"')" - + if context.get('periods', False): - ids = ','.join([str(x) for x in context['periods']]) + ids = ','.join([str(x) for x in context['periods']]) return obj+".active AND "+obj+".state<>'draft' AND "+obj+".period_id in (SELECT id from account_period WHERE fiscalyear_id in (%s) AND id in (%s)) %s" % (fiscalyear_clause, ids,where_move_state) else: return obj+".active AND "+obj+".state<>'draft' AND "+obj+".period_id in (SELECT id from account_period WHERE fiscalyear_id in (%s) %s)" % (fiscalyear_clause,where_move_state) @@ -71,6 +71,9 @@ class account_move_line(osv.osv): return data period_obj = self.pool.get('account.period') + tax_obj=self.pool.get('account.tax') + + tax_line=context.get('tax_line',False) # Compute the current move move_id = False @@ -86,6 +89,7 @@ class account_move_line(osv.osv): (context['journal_id'], context['period_id'], uid, 'draft')) res = cr.fetchone() move_id = (res and res[0]) or False + if not move_id: return data else: @@ -113,10 +117,33 @@ class account_move_line(osv.osv): ref_id = False taxes = {} move = self.pool.get('account.move').browse(cr, uid, move_id, context) + for l in move.line_id: partner_id = partner_id or l.partner_id.id ref_id = ref_id or l.ref - total += (l.debit - l.credit) + + final_debit=l.debit + final_credit=l.credit + + if 'name' in fields: + data.setdefault('name', l.name) + + if l.amount_taxed: + if l.debit: + final_debit=l.amount_taxed + if not tax_line: + final_debit= l.debit - final_debit + + if l.credit: + final_credit=l.amount_taxed + if not tax_line: + final_credit =l.credit - final_credit + total += (final_debit - final_credit) + + else: + if len(move.line_id)==1: + total += (final_debit - final_credit) + for tax in l.account_id.tax_ids: if move.journal_id.type == 'sale': if l.debit: @@ -134,8 +161,7 @@ class account_move_line(osv.osv): acc = tax.account_paid_id.id taxes.setdefault((acc, code), False) taxes[(l.account_id.id, l.tax_code_id.id)] = True - if 'name' in fields: - data.setdefault('name', l.name) + if 'ref' in fields: data['ref'] = ref_id @@ -172,7 +198,9 @@ class account_move_line(osv.osv): s -= tax['amount'] tax_amount += tax['amount'] * \ tax[field_base + 'tax_sign'] + if ('debit' in fields) or ('credit' in fields): + data['debit'] = s>0 and s or 0.0 data['credit'] = s<0 and -s or 0.0 @@ -334,6 +362,9 @@ class account_move_line(osv.osv): 'tax_amount': fields.float('Tax/Base Amount', digits=(16,2), select=True), 'invoice': fields.function(_invoice, method=True, string='Invoice', type='many2one', relation='account.invoice', fnct_search=_invoice_search), + 'account_tax_id':fields.many2one('account.tax', 'Tax'), + 'analytic_account_id' : fields.many2one('account.analytic.account', 'Analytic Account'), + 'amount_taxed':fields.float("Taxed Amount",digits=(16,2)), } def _get_date(self, cr, uid, context): @@ -632,7 +663,19 @@ class account_move_line(osv.osv): def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True): if not context: context={} + raise_ex=False account_obj = self.pool.get('account.account') + acc=account_obj.browse(cr,uid,ids)[0] + + if ('debit' in vals and 'credit' in vals) and not vals['debit'] and not vals['credit']: + raise_ex=True + if ('debit' in vals and 'credit' not in vals) and not vals['debit'] and not acc.credit: + raise_ex=True + if ('credit' in vals and 'debit' not in vals) and not vals['credit'] and not acc.debit: + raise_ex=True + + if raise_ex: + raise osv.except_osv(_('Wrong Accounting Entry!'), _('Both Credit and Debit cannot be zero!')) if ('account_id' in vals) and not account_obj.read(cr, uid, vals['account_id'], ['active'])['active']: raise osv.except_osv(_('Bad account!'), _('You can not use an inactive account!')) @@ -680,7 +723,11 @@ class account_move_line(osv.osv): def create(self, cr, uid, vals, context=None, check=True): if not context: context={} + + if not vals['debit'] and not vals['credit']: + raise osv.except_osv(_('Wrong Accounting Entry!'), _('Both Credit and Debit cannot be zero!')) account_obj = self.pool.get('account.account') + tax_obj=self.pool.get('account.tax') if ('account_id' in vals) and not account_obj.read(cr, uid, vals['account_id'], ['active'])['active']: raise osv.except_osv(_('Bad account!'), _('You can not use an inactive account!')) @@ -743,7 +790,26 @@ class account_move_line(osv.osv): vals['amount_currency'] = cur_obj.compute(cr, uid, account.company_id.currency_id.id, account.currency_id.id, vals.get('debit', 0.0)-vals.get('credit', 0.0), context=ctx) if not ok: raise osv.except_osv(_('Bad account !'), _('You can not use this general account in this journal !')) + + tax_go=False + if 'account_tax_id' in vals and vals['account_tax_id']: + tax_id=tax_obj.browse(cr,uid,vals['account_tax_id']) + + if vals['debit']: + vals['amount_taxed']=tax_obj.compute(cr,uid,[tax_id],vals['debit'],1.00)[0]['amount'] + vals['debit'] +=vals['amount_taxed'] + if vals['credit']: + vals['amount_taxed']=tax_obj.compute(cr,uid,[tax_id],vals['credit'],1.00)[0]['amount'] + vals['credit'] +=vals['amount_taxed'] + fields=[x for x in vals] + tax_go=True + result = super(osv.osv, self).create(cr, uid, vals, context) + if tax_go: + context['tax_line']=True + if vals['amount_taxed']: + data_tax=self._default_get(cr, uid, fields, context=context) + self.create(cr,uid,data_tax) if check: self.pool.get('account.move').validate(cr, uid, [vals['move_id']], context) return result diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index 1b50734c4b6..07d6c1143e4 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -674,6 +674,8 @@ + + @@ -716,6 +718,9 @@ + + + @@ -764,6 +769,9 @@ + + + @@ -1365,6 +1373,8 @@ + + diff --git a/addons/account/data/account_data2.xml b/addons/account/data/account_data2.xml index 9f51843efd2..02475a31902 100644 --- a/addons/account/data/account_data2.xml +++ b/addons/account/data/account_data2.xml @@ -1,10 +1,10 @@ - + + --> 30 Days Net @@ -15,7 +15,7 @@ net days - + @@ -74,6 +74,18 @@ credit + + + Tax + account_tax_id + + + + + Analytic Account + analytic_account_id + + Ref @@ -84,9 +96,9 @@ State state - + - + Multi-Currency Cash Journal View @@ -142,17 +154,29 @@ credit + + + Tax + account_tax_id + + + + + Analytic Account + analytic_account_id + + Currency Amt. amount_currency - + Currency currency_id - + @@ -164,10 +188,10 @@ State state - + - - + + Journal View @@ -229,30 +253,42 @@ credit + + + Tax + account_tax_id + + + + + Analytic Account + analytic_account_id + + Tax Acc. tax_code_id - + Tax tax_amount - + State state - + - - + + - + Account Journal account.journal @@ -262,11 +298,11 @@ account.journal - + - + Account reconcile sequence account.reconcile @@ -278,8 +314,8 @@ - - + + Bank Statement account.bank.statement @@ -291,11 +327,11 @@ - + - + Sales Journal SAJ @@ -316,7 +352,7 @@ - + Bank Journal BNK @@ -327,7 +363,7 @@ - + Stock Journal STJ @@ -336,6 +372,6 @@ - + \ No newline at end of file From 3a335d6c4c1582d26347d15b8de7aa3742a0957e Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Thu, 14 Aug 2008 15:49:58 +0200 Subject: [PATCH 024/169] Open Invoice -> Unpaid Invoice bzr revid: fp@tinyerp.com-20080814134958-vwcsp45krgmdr8t2 --- addons/account/account_invoice_view.xml | 20 ++++++++++---------- addons/account/invoice.py | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 57b06d2719c..76014a26065 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -405,10 +405,10 @@ - Open Customer Invoices + Unpaid Customer Invoices account.invoice form - [('state','=','open'),('type','=','out_invoice')] + [('state','<>','draft'),('reconciled','=',False),('type','=','out_invoice')] {'type':'out_invoice'} @@ -447,10 +447,10 @@ - Open Supplier Invoices + Unpaid Supplier Invoices account.invoice form - [('state','=','open'),('type','=','in_invoice')] + [('state','<>','draft'),('reconciled','=',False),('type','=','in_invoice')] {'type':'in_invoice'} @@ -489,10 +489,10 @@ - Open Customer Refunds + Unpaid Customer Refunds account.invoice form - [('state','=','open'),('type','=','out_refund')] + [('state','<>','draft'),('reconciled','=',False),('type','=','out_refund')] {'type':'out_refund'} @@ -531,10 +531,10 @@ - Open Supplier Refunds + Unpaid Supplier Refunds account.invoice form - [('state','=','open'),('type','=','in_refund')] + [('state','<>','draft'),('reconciled','=',False),('type','=','in_refund')] {'type':'in_refund'} @@ -551,9 +551,9 @@ - + - + diff --git a/addons/account/invoice.py b/addons/account/invoice.py index 01239c0c9af..b124bf5a602 100644 --- a/addons/account/invoice.py +++ b/addons/account/invoice.py @@ -122,7 +122,7 @@ class account_invoice(osv.osv): ('draft','Draft'), ('proforma','Pro-forma'), ('open','Open'), - ('paid','Paid'), + ('paid','Done'), ('cancel','Canceled') ],'State', select=True, readonly=True), @@ -149,9 +149,9 @@ class account_invoice(osv.osv): 'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}), 'company_id': fields.many2one('res.company', 'Company', required=True), 'check_total': fields.float('Total', digits=(16,2), states={'open':[('readonly',True)],'close':[('readonly',True)]}), - 'reconciled': fields.function(_reconciled, method=True, string='Reconciled', type='boolean'), + 'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean'), 'partner_bank': fields.many2one('res.partner.bank', 'Bank Account', - help='The bank account to pay or to be paid'), + help='The bank account to pay to or to be paid from'), } _defaults = { 'type': _get_type, From c870b69852c9f6212804e180fdfa3cc0100b57d2 Mon Sep 17 00:00:00 2001 From: "hda@tinyerp.com" <> Date: Thu, 14 Aug 2008 19:22:05 +0530 Subject: [PATCH 025/169] added rng grammer for xml validation on views bzr revid: hda@tinyerp.com-20080814135205-dq6s3zlv7x08998j --- bin/addons/base/ir/ir_ui_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/addons/base/ir/ir_ui_view.py b/bin/addons/base/ir/ir_ui_view.py index 7066d429bd4..2a65a8378ed 100644 --- a/bin/addons/base/ir/ir_ui_view.py +++ b/bin/addons/base/ir/ir_ui_view.py @@ -37,7 +37,7 @@ import os def _check_xml(self, cr, uid, ids, context={}): for view in self.browse(cr, uid, ids, context): eview = etree.fromstring(view.arch) - frng = tools.file_open(os.path.join('base','rng',view.type+'.rng')) + frng = tools.file_open(os.path.join('base','rng','view.rng')) relaxng = etree.RelaxNG(file=frng) if not relaxng.validate(eview): logger = netsvc.Logger() From c6d2067c344bc3af6d01858739688beeec907140 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 14 Aug 2008 17:06:20 +0200 Subject: [PATCH 026/169] bugfix: change variable usage bzr revid: christophe@tinyerp.com-20080814150620-m9kxun5snnjffu3r --- bin/osv/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/osv/expression.py b/bin/osv/expression.py index f3a15450ace..49bdb50f755 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -69,7 +69,7 @@ class expression(object): left, operator, right = e if left in table._inherit_fields: - working_table = table.pool.get(table._inherit_fields[self.__left][0]) + working_table = table.pool.get(table._inherit_fields[left][0]) if working_table not in self.__tables.values(): self.__joins.append('%s.%s' % (table._table, table._inherits[working_table._name])) From 985c9f025e14417537ca6fe90957516b79644191 Mon Sep 17 00:00:00 2001 From: Fabian Semal Date: Thu, 14 Aug 2008 17:39:28 +0200 Subject: [PATCH 027/169] Changed '<>' by '!=' for rng bzr revid: fabian@tinyerp.com-20080814153928-ios9rymty7n6jau9 --- addons/account/account_invoice_view.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 76014a26065..a74e82ef4a1 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -408,7 +408,7 @@ Unpaid Customer Invoices account.invoice form - [('state','<>','draft'),('reconciled','=',False),('type','=','out_invoice')] + [('state','!=','draft'),('reconciled','=',False),('type','=','out_invoice')] {'type':'out_invoice'} @@ -450,7 +450,7 @@ Unpaid Supplier Invoices account.invoice form - [('state','<>','draft'),('reconciled','=',False),('type','=','in_invoice')] + [('state','!=','draft'),('reconciled','=',False),('type','=','in_invoice')] {'type':'in_invoice'} @@ -492,7 +492,7 @@ Unpaid Customer Refunds account.invoice form - [('state','<>','draft'),('reconciled','=',False),('type','=','out_refund')] + [('state','!=','draft'),('reconciled','=',False),('type','=','out_refund')] {'type':'out_refund'} @@ -534,7 +534,7 @@ Unpaid Supplier Refunds account.invoice form - [('state','<>','draft'),('reconciled','=',False),('type','=','in_refund')] + [('state','!=','draft'),('reconciled','=',False),('type','=','in_refund')] {'type':'in_refund'} @@ -551,9 +551,9 @@ - + - + From c928afe1bcbb8e2a48e74ca14ec8942ec9f7d4a1 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Thu, 14 Aug 2008 17:53:27 +0200 Subject: [PATCH 028/169] add view.rng from hda@tinyerp.com - Husen Daudi bzr revid: stephane@tinyerp.com-20080814155327-bk37uc7tx2s4rdnd --- bin/addons/base/rng/view.rng | 247 +++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 bin/addons/base/rng/view.rng diff --git a/bin/addons/base/rng/view.rng b/bin/addons/base/rng/view.rng new file mode 100644 index 00000000000..d9fc926f204 --- /dev/null +++ b/bin/addons/base/rng/view.rng @@ -0,0 +1,247 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 5977152d8ad5b1fb64b63b2632333aeeaff95c8b Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Thu, 14 Aug 2008 18:41:17 +0200 Subject: [PATCH 029/169] bugfix with the working table in an expression bzr revid: stephane@tinyerp.com-20080814164117-pcrx76mvis7n583x --- bin/osv/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/osv/expression.py b/bin/osv/expression.py index 49bdb50f755..11c50c51be8 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -61,13 +61,13 @@ class expression(object): return self self.__main_table = table - working_table = table for i, e in enumerate(self.__exp): if self._is_operator(e) or e == self.__DUMMY_LEAF: continue left, operator, right = e + working_table = table if left in table._inherit_fields: working_table = table.pool.get(table._inherit_fields[left][0]) if working_table not in self.__tables.values(): From 32858905760350e8583686fd4bc4b2743c0f2a1d Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Thu, 14 Aug 2008 19:05:08 +0200 Subject: [PATCH 030/169] Speed Improvement: faster child of bzr revid: fp@tinyerp.com-20080814170508-r2rqh3ejju2qdtrp --- bin/addons/base/ir/ir_ui_view.py | 1 - bin/osv/expression.py | 16 ++++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/bin/addons/base/ir/ir_ui_view.py b/bin/addons/base/ir/ir_ui_view.py index 2a65a8378ed..309841a7ac6 100644 --- a/bin/addons/base/ir/ir_ui_view.py +++ b/bin/addons/base/ir/ir_ui_view.py @@ -43,7 +43,6 @@ def _check_xml(self, cr, uid, ids, context={}): logger = netsvc.Logger() logger.notifyChannel('init', netsvc.LOG_ERROR, 'The view do not fit the required schema !') logger.notifyChannel('init', netsvc.LOG_ERROR, relaxng.error_log.last_error) - print view.arch return False return True diff --git a/bin/osv/expression.py b/bin/osv/expression.py index 49bdb50f755..ccac1bda1fa 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -52,10 +52,18 @@ class expression(object): """ transform the leafs of the expression """ def _rec_get(ids, table, parent): - if not ids: - return [] - ids2 = table.search(cr, uid, [(parent, 'in', ids)], context=context) - return ids + _rec_get(ids2, table, parent) + if table._parent_store: + doms = [] + for o in table.browse(cr, uid, ids, context=context): + if doms: + doms.insert(0,'|') + doms.append(['&',('parent_left','<',o.parent_right),('parent_left','>=',o.parent_left)]) + return table.search(cr, uid, doms, context=context) + else: + if not ids: + return [] + ids2 = table.search(cr, uid, [(parent, 'in', ids)], context=context) + return ids + _rec_get(ids2, table, parent) if not self.__exp: return self From 61d07d0771a4bf1aad097b3b6091c0f3454fae23 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Sat, 16 Aug 2008 18:30:06 +0200 Subject: [PATCH 031/169] Bugfixes bzr revid: fp@tinyerp.com-20080816163006-aspcc7f5dmv0g4ri --- bin/addons/base/res/res_company.py | 4 ++-- bin/osv/expression.py | 20 ++++++++------------ bin/osv/orm.py | 2 +- bin/sql_db.py | 1 + 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/bin/addons/base/res/res_company.py b/bin/addons/base/res/res_company.py index 90cb69185a5..f7e6f94c342 100644 --- a/bin/addons/base/res/res_company.py +++ b/bin/addons/base/res/res_company.py @@ -97,9 +97,9 @@ class res_company(osv.osv): def _get_euro(self, cr, uid, context={}): try: - return self.pool.get('res.currency').search(cr, uid, [('rate', '=', 1.0),])[0] + return self.pool.get('res.currency').search(cr, uid, [])[0] except: - return 1 + return False def _check_recursion(self, cr, uid, ids): level = 100 diff --git a/bin/osv/expression.py b/bin/osv/expression.py index d04b4cd7f92..842dabf031f 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -35,21 +35,20 @@ class expression(object): def __init__(self, exp): - # check if the expression is valid if not reduce(lambda acc, val: acc and (self._is_operator(val) or self._is_leaf(val)), exp, True): - raise ValueError('Bad expression: %r' % (exp,)) - + raise ValueError('Bad domain expression: %r' % (exp,)) self.__exp = exp self.__tables = {} # used to store the table to use for the sql generation. key = index of the leaf self.__joins = [] self.__main_table = None # 'root' table. set by parse() - self.__DUMMY_LEAF = (1, '=', 1) # a dummy leaf that must not be parsed or sql generated def parse(self, cr, uid, table, context): """ transform the leafs of the expression """ + if not self.__exp: + return self def _rec_get(ids, table, parent): if table._parent_store: @@ -57,7 +56,7 @@ class expression(object): for o in table.browse(cr, uid, ids, context=context): if doms: doms.insert(0,'|') - doms.append(['&',('parent_left','<',o.parent_right),('parent_left','>=',o.parent_left)]) + doms += ['&',('parent_left','<',o.parent_right),('parent_left','>=',o.parent_left)] return table.search(cr, uid, doms, context=context) else: if not ids: @@ -65,11 +64,8 @@ class expression(object): ids2 = table.search(cr, uid, [(parent, 'in', ids)], context=context) return ids + _rec_get(ids2, table, parent) - if not self.__exp: - return self - self.__main_table = table - + for i, e in enumerate(self.__exp): if self._is_operator(e) or e == self.__DUMMY_LEAF: continue @@ -80,9 +76,9 @@ class expression(object): working_table = table.pool.get(table._inherit_fields[left][0]) if working_table not in self.__tables.values(): self.__joins.append('%s.%s' % (table._table, table._inherits[working_table._name])) - + self.__tables[i] = working_table - + fargs = left.split('.', 1) field = working_table._columns.get(fargs[0], False) if not field: @@ -90,7 +86,7 @@ class expression(object): right += _rec_get(right, working_table, working_table._parent_name) self.__exp[i] = ('id', 'in', right) continue - + field_obj = table.pool.get(field._obj) if len(fargs) > 1: if field._type == 'many2one': diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 3fa74f280c3..acb4d4fb025 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -1375,7 +1375,7 @@ class orm(orm_template): if not default: cr.execute("UPDATE \"%s\" SET \"%s\"=NULL" % (self._table, k)) else: - cr.execute("UPDATE \"%s\" SET \"%s\"='%s'" % (self._table, k, default)) + cr.execute("UPDATE \"%s\" SET \"%s\"=%%s" % (self._table, k), (default,)) if isinstance(f, fields.function): cr.execute('select id from '+self._table) ids_lst = map(lambda x: x[0], cr.fetchall()) diff --git a/bin/sql_db.py b/bin/sql_db.py index 05a5577490c..9da6293cfac 100644 --- a/bin/sql_db.py +++ b/bin/sql_db.py @@ -59,6 +59,7 @@ class fake_cursor: self.dbname = dbname def execute(self, sql, params=None): + print sql, params if not params: params=() def base_string(s): From d88fb99fe46ed2212d9f6ee622d10ec8e9f00de7 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Sun, 17 Aug 2008 20:28:29 +0200 Subject: [PATCH 032/169] Speed Improvement: Nested Trees New Domains Bugfixes Compute multiple function fields at once (multi) bzr revid: fp@tinyerp.com-20080817182829-dc60s4srja6ck0bw --- bin/addons/__init__.py | 35 ++++++----- bin/osv/expression.py | 33 +++++----- bin/osv/fields.py | 11 ++-- bin/osv/orm.py | 140 ++++++++++++++++++++++++----------------- bin/osv/osv.py | 13 ++++ bin/pooler.py | 4 ++ bin/sql_db.py | 1 - 7 files changed, 145 insertions(+), 92 deletions(-) diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index 156688c322a..74ecc9a6f04 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -270,25 +270,26 @@ def load_module_graph(cr, graph, status=None, **kwargs): cr.execute('update ir_module_module set demo=%s where name=%s', (True, package.name)) package_todo.append(package.name) cr.execute("update ir_module_module set state='installed' where state in ('to upgrade', 'to install') and name=%s", (package.name,)) - - # check if all model of the module have at least a access rule. - cr.execute(""" SELECT name - FROM ir_model m - WHERE EXISTS (SELECT 1 - FROM ir_model_data - WHERE module = %s - AND model = m.name - ) - AND NOT EXISTS (SELECT 1 - FROM ir_model_access - WHERE model_id = m.id - ) - """, (m,)) - for (model,) in cr.fetchall(): - logger.notifyChannel('init', netsvc.LOG_WARNING, 'addon:%s:object %s has no access rules!' % (m,model,)) + # check if all model of the module have at least a access rule. + # TODO: improve this query which is very slow !!! + cr.execute(""" SELECT name + FROM ir_model m + WHERE EXISTS (SELECT 1 + FROM ir_model_data + WHERE module = %s + AND model = m.name + ) + AND NOT EXISTS (SELECT 1 + FROM ir_model_access + WHERE model_id = m.id + ) + """, (m,)) - cr.commit() + for (model,) in cr.fetchall(): + logger.notifyChannel('init', netsvc.LOG_WARNING, 'addon:%s:object %s has no access rules!' % (m,model,)) + + cr.commit() statusi+=1 pool = pooler.get_pool(cr.dbname) diff --git a/bin/osv/expression.py b/bin/osv/expression.py index 842dabf031f..b98c6fac9fc 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -17,12 +17,11 @@ class expression(object): and element in ['&', '|', '!'] def _is_leaf(self, element): - return isinstance(element, tuple) \ + return (isinstance(element, tuple) or isinstance(element, list)) \ and len(element) == 3 \ - and element[1] in ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of') + and element[1] in ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of', 'inselect') def __execute_recursive_in(self, cr, s, f, w, ids): - #deprecated -> use _left and _right... res = [] for i in range(0, len(ids), cr.IN_MAX): subids = ids[i:i+cr.IN_MAX] @@ -50,23 +49,30 @@ class expression(object): if not self.__exp: return self - def _rec_get(ids, table, parent): - if table._parent_store: + def _rec_get(ids, table, parent, left='id', prefix=''): + if table._parent_store and (not table.pool._init): +# TODO: Improve where joins are implemented for many with '.', replace by: +# doms += ['&',(prefix+'.parent_left','<',o.parent_right),(prefix+'.parent_left','>=',o.parent_left)] doms = [] for o in table.browse(cr, uid, ids, context=context): if doms: doms.insert(0,'|') doms += ['&',('parent_left','<',o.parent_right),('parent_left','>=',o.parent_left)] - return table.search(cr, uid, doms, context=context) + if prefix: + return [(left, 'in', table.search(cr, uid, doms, context=context))] + return doms else: if not ids: return [] ids2 = table.search(cr, uid, [(parent, 'in', ids)], context=context) - return ids + _rec_get(ids2, table, parent) + return [(prefix+left, 'in', ids2+ids)] self.__main_table = table - for i, e in enumerate(self.__exp): + i = -1 + while i+1 pleft: - treeshift = pleft - cleft + 1 - leftbound = pleft+1 - rightbound = cleft-1 - cwidth = cright-cleft+1 - leftrange = cright - rightrange = pleft + cr.execute('select parent_left,parent_right from '+self._table+' where id=%d', (vals[self._parent_name],)) + res = cr.fetchone() + if res: + pleft,pright = res else: - treeshift = pleft - cright - leftbound = cright + 1 - rightbound = pleft - cwidth = cleft-cright-1 - leftrange = pleft+1 - rightrange = cleft - cr.execute('UPDATE '+self._table+''' - SET - parent_left = CASE - WHEN parent_left BETWEEN %d AND %d THEN parent_left + %d - WHEN parent_left BETWEEN %d AND %d THEN parent_left + %d - ELSE parent_left - END, - parent_right = CASE - WHEN parent_right BETWEEN %d AND %d THEN parent_right + %d - WHEN parent_right BETWEEN %d AND %d THEN parent_right + %d - ELSE parent_right - END - WHERE - parent_left<%d OR parent_right>%d; - ''', (leftbound,rightbound,cwidth,cleft,cright,treeshift,leftbound,rightbound, - cwidth,cleft,cright,treeshift,leftrange,rightrange)) + cr.execute('select max(parent_right),max(parent_right)+1 from '+self._table) + pleft,pright = cr.fetchone() + cr.execute('select parent_left,parent_right,id from '+self._table+' where id in ('+','.join(map(lambda x:'%d',ids))+')', ids) + dest = pleft + 1 + for cleft,cright,cid in cr.fetchall(): + if cleft > pleft: + treeshift = pleft - cleft + 1 + leftbound = pleft+1 + rightbound = cleft-1 + cwidth = cright-cleft+1 + leftrange = cright + rightrange = pleft + else: + treeshift = pleft - cright + leftbound = cright + 1 + rightbound = pleft + cwidth = cleft-cright-1 + leftrange = pleft+1 + rightrange = cleft + cr.execute('UPDATE '+self._table+''' + SET + parent_left = CASE + WHEN parent_left BETWEEN %d AND %d THEN parent_left + %d + WHEN parent_left BETWEEN %d AND %d THEN parent_left + %d + ELSE parent_left + END, + parent_right = CASE + WHEN parent_right BETWEEN %d AND %d THEN parent_right + %d + WHEN parent_right BETWEEN %d AND %d THEN parent_right + %d + ELSE parent_right + END + WHERE + parent_left<%d OR parent_right>%d; + ''', (leftbound,rightbound,cwidth,cleft,cright,treeshift,leftbound,rightbound, + cwidth,cleft,cright,treeshift,leftrange,rightrange)) if 'read_delta' in context: del context['read_delta'] @@ -2206,16 +2231,19 @@ class orm(orm_template): self._validate(cr, user, [id_new], context) if self._parent_store: - parent = vals.get(self._parent_name, False) - if parent: - cr.execute('select parent_left from '+self._table+' where id=%d', (parent,)) - pleft = cr.fetchone()[0] + if self.pool._init: + self.pool._init_parent[self._name]=True else: - cr.execute('select max(parent_right) from '+self._table) - pleft = cr.fetchone()[0] or 0 - cr.execute('update '+self._table+' set parent_left=%d,parent_right=%d', (pleft+1,pleft+2)) - cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%d', (pleft,)) - cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%d', (pleft,)) + parent = vals.get(self._parent_name, False) + if parent: + cr.execute('select parent_left from '+self._table+' where id=%d', (parent,)) + pleft = cr.fetchone()[0] + else: + cr.execute('select max(parent_right) from '+self._table) + pleft = cr.fetchone()[0] or 0 + cr.execute('update '+self._table+' set parent_left=%d,parent_right=%d', (pleft+1,pleft+2)) + cr.execute('update '+self._table+' set parent_left=parent_left+2 where parent_left>%d', (pleft,)) + cr.execute('update '+self._table+' set parent_right=parent_right+2 where parent_right>%d', (pleft,)) wf_service = netsvc.LocalService("workflow") wf_service.trg_create(user, self._name, id_new, cr) @@ -2281,7 +2309,7 @@ class orm(orm_template): # records unless they were explicitely asked for if 'active' in self._columns and (active_test and context.get('active_test', True)): if args: - args = ['&', ('active', '=', 1)] + args + args.insert(0, ('active', '=', 1)) else: args = [('active', '=', 1)] diff --git a/bin/osv/osv.py b/bin/osv/osv.py index 369f006c9df..8e2944eed76 100644 --- a/bin/osv/osv.py +++ b/bin/osv/osv.py @@ -60,6 +60,8 @@ class osv_pool(netsvc.Service): self.created = [] self._sql_error = {} self._store_function = {} + self._init = True + self._init_parent = {} netsvc.Service.__init__(self, 'object_proxy', audience='') self.joinGroup('web-services') self.exportMethod(self.exportedMethods) @@ -68,6 +70,17 @@ class osv_pool(netsvc.Service): self.exportMethod(self.execute) self.exportMethod(self.execute_cr) + def init_set(self, cr, mode): + if mode<>self._init: + if mode: + self._init_parent={} + if not mode: + for o in self._init_parent: + self.get(o)._parent_store_compute(cr) + self._init = mode + return True + return False + def execute_cr(self, cr, uid, obj, method, *args, **kw): try: object = pooler.get_pool(cr.dbname).get(obj) diff --git a/bin/pooler.py b/bin/pooler.py index e5c5b82be98..fd23a9edaae 100644 --- a/bin/pooler.py +++ b/bin/pooler.py @@ -54,6 +54,10 @@ def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False) pool = osv.osv.osv_pool() pool_dic[db_name] = pool addons.load_modules(db, force_demo, status, update_module) + cr = db.cursor() + pool.init_set(cr, False) + cr.commit() + cr.close() if not update_module: import report diff --git a/bin/sql_db.py b/bin/sql_db.py index 9da6293cfac..05a5577490c 100644 --- a/bin/sql_db.py +++ b/bin/sql_db.py @@ -59,7 +59,6 @@ class fake_cursor: self.dbname = dbname def execute(self, sql, params=None): - print sql, params if not params: params=() def base_string(s): From f7e6fcc3ce6af506e54a3177cde7a333e91eda78 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Sun, 17 Aug 2008 20:29:44 +0200 Subject: [PATCH 033/169] Speed Improvement: Nested Trees Multiple Fields Computation at once bzr revid: fp@tinyerp.com-20080817182944-vq0kp64i93ceull9 --- addons/account/account.py | 113 +++++++++------------------- addons/account/account_move_line.py | 4 +- 2 files changed, 37 insertions(+), 80 deletions(-) diff --git a/addons/account/account.py b/addons/account/account.py index aa22b940528..d9fba92ef14 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -156,89 +156,46 @@ class account_account(osv.osv): return super(account_account,self).search(cr, uid, args, offset, limit, order, context=context, count=count) - def _credit(self, cr, uid, ids, field_name, arg, context={}): - acc_set = ",".join(map(str, ids)) - query = self.pool.get('account.move.line')._query_get(cr, uid, context=context) - cr.execute(("SELECT a.id, " \ - "SUM(COALESCE(l.credit * a.sign, 0)) " \ - "FROM account_account a " \ - "LEFT JOIN account_move_line l " \ - "ON (a.id = l.account_id) " \ - "WHERE a.type != 'view' " \ - "AND a.id IN (%s) " \ - "AND " + query + " " \ - "AND a.active " \ - "GROUP BY a.id") % (acc_set, )) - res2 = cr.fetchall() - res = {} - for id in ids: - res[id] = 0.0 - for account_id, sum in res2: - res[account_id] += sum - return res +# def _credit(self, cr, uid, ids, field_name, arg, context={}): +# return self.__compute(cr, uid, ids, field_name, arg, context, 'COALESCE(SUM(l.credit), 0)') +# +# def _debit(self, cr, uid, ids, field_name, arg, context={}): +# return self.__compute(cr, uid, ids, field_name, arg, context, 'COALESCE(SUM(l.debit), 0)') +# +# def _balance(self, cr, uid, ids, field_name, arg, context={}): +# return self.__compute(cr, uid, ids, field_name, arg, context, 'COALESCE(SUM(l.debit) - SUM(l.credit), 0)') - def _debit(self, cr, uid, ids, field_name, arg, context={}): - acc_set = ",".join(map(str, ids)) - query = self.pool.get('account.move.line')._query_get(cr, uid, context=context) - cr.execute(("SELECT a.id, " \ - "SUM(COALESCE(l.debit * a.sign, 0)) " \ - "FROM account_account a " \ - "LEFT JOIN account_move_line l " \ - "ON (a.id = l.account_id) " \ - "WHERE a.type != 'view' " \ - "AND a.id IN (%s) " \ - "AND " + query + " " \ - "AND a.active " \ - "GROUP BY a.id") % (acc_set, )) - res2 = cr.fetchall() - res = {} - for id in ids: - res[id] = 0.0 - for account_id, sum in res2: - res[account_id] += sum - return res - - def _balance(self, cr, uid, ids, field_name, arg, context={}): + def __compute(self, cr, uid, ids, field_names, arg, context={}, query=''): + mapping = { + 'balance': "COALESCE(SUM(l.debit) - SUM(l.credit), 0) as balance ", + 'debit': "COALESCE(SUM(l.debit), 0) as debit ", + 'credit': "COALESCE(SUM(l.credit), 0) as credit " + } ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)]) - ids2 = {}.fromkeys(ids + ids2).keys() acc_set = ",".join(map(str, ids2)) query = self.pool.get('account.move.line')._query_get(cr, uid, context=context) - cr.execute(("SELECT a.id, " \ - "SUM((COALESCE(l.debit, 0) - COALESCE(l.credit, 0))) " \ - "FROM account_account a " \ - "LEFT JOIN account_move_line l " \ - "ON (a.id=l.account_id) " \ - "WHERE a.type != 'view' " \ - "AND a.id IN (%s) " \ + cr.execute(("SELECT l.account_id, " +\ + ' , '.join(map(lambda x: mapping[x], field_names)) + + "FROM " \ + "account_move_line l " \ + "WHERE " \ + "l.account_id IN (%s) " \ "AND " + query + " " \ - "AND a.active " \ - "GROUP BY a.id") % (acc_set, )) - res = {} - for account_id, sum in cr.fetchall(): - res[account_id] = round(sum,2) - cr.execute("SELECT a.id, a.company_id " \ - "FROM account_account a " \ - "WHERE id IN (%s)" % acc_set) - resc = dict(cr.fetchall()) - cr.execute("SELECT id, currency_id FROM res_company") - rescur = dict(cr.fetchall()) + "GROUP BY l.account_id") % (acc_set, )) + accounts = {} + for res in cr.fetchall(): + accounts[res[0]] = res[1:] + + res = {} for id in ids: - ids3 = self.search(cr, uid, [('parent_id', 'child_of', [id])]) - to_currency_id = rescur[resc[id]] - for idx in ids3: - if idx <> id: - res.setdefault(id, 0.0) - if resc[idx]<>resc[id] and resc[idx] and resc[id]: - from_currency_id = rescur[resc[idx]] - res[id] += self.pool.get('res.currency').compute(cr, - uid, from_currency_id, to_currency_id, - res.get(idx, 0.0), context=context) - else: - res[id] += res.get(idx, 0.0) - for id in ids: - res[id] = round(res.get(id,0.0), 2) + res[id] = map(lambda x: 0.0, field_names) + ids2 = self.search(cr, uid, [('parent_id', 'child_of', [id])]) + for i in ids2: + for a in range(len(field_names)): + res[id][a] += accounts.get(i, (0.0,0.0,0.0))[a] + print res return res def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}): @@ -274,9 +231,9 @@ class account_account(osv.osv): 'child_id': fields.function(_get_child_ids, method=True, type='many2many',relation="account.account",string="Children Accounts"), # 'child_id': fields.many2many('account.account', 'account_account_rel', 'parent_id', 'child_id', 'Children'), - 'balance': fields.function(_balance, digits=(16,2), method=True, string='Balance'), - 'credit': fields.function(_credit, digits=(16,2), method=True, string='Credit'), - 'debit': fields.function(_debit, digits=(16,2), method=True, string='Debit'), + 'balance': fields.function(__compute, digits=(16,2), method=True, string='Balance', multi='balance'), + 'credit': fields.function(__compute, digits=(16,2), method=True, string='Credit', multi='balance'), + 'debit': fields.function(__compute, digits=(16,2), method=True, string='Debit', multi='balance'), 'reconcile': fields.boolean('Reconcile', help="Check this account if the user can make a reconciliation of the entries in this account."), 'shortcut': fields.char('Shortcut', size=12), 'close_method': fields.selection([('none','None'), ('balance','Balance'), ('detail','Detail'),('unreconciled','Unreconciled')], 'Deferral Method', required=True, help="Tell Tiny ERP how to process the entries of this account when you close a fiscal year. None removes all entries to start with an empty account for the new fiscal year. Balance creates only one entry to keep the balance for the new fiscal year. Detail keeps the detail of all entries of the preceeding years. Unreconciled keeps the detail of unreconciled entries only."), diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index b93afede487..a5ede47259c 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -317,8 +317,8 @@ class account_move_line(osv.osv): 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optionnal other currency if it is a multi-currency entry."), 'currency_id': fields.many2one('res.currency', 'Currency', help="The optionnal other currency if it is a multi-currency entry."), - 'period_id': fields.many2one('account.period', 'Period', required=True), - 'journal_id': fields.many2one('account.journal', 'Journal', required=True), + 'period_id': fields.many2one('account.period', 'Period', required=True, select=2), + 'journal_id': fields.many2one('account.journal', 'Journal', required=True, select=1), 'blocked': fields.boolean('Litigation', help="You can check this box to mark the entry line as a litigation with the associated partner"), 'partner_id': fields.many2one('res.partner', 'Partner Ref.'), From 484b2197ede26a41f8219744fdbf7a0c7cd1794c Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Sun, 17 Aug 2008 20:31:16 +0200 Subject: [PATCH 034/169] Removed Print Statement bzr revid: fp@tinyerp.com-20080817183116-0zfefou6l4kn5byf --- bin/osv/orm.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index eed40b327db..8cfa2599472 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -1764,17 +1764,13 @@ class orm(orm_template): for f in fields_post: todo.setdefault(self._columns[f]._multi, []) todo[self._columns[f]._multi].append(f) - print 'TODO', todo for key,val in todo.items(): if key: - print '* KEY', key res2 = self._columns[val[0]].get(cr, self, ids, val, user, context=context, values=res) - print '* KEY RES', res2, val, ids for pos in range(len(val)): for record in res: record[val[pos]] = res2[record['id']][pos] else: - print 'KEY NOT', key for f in val: res2 = self._columns[f].get(cr, self, ids, f, user, context=context, values=res) for record in res: From ae36063314b917031b708b0cfa582fdccb6f96c2 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Mon, 18 Aug 2008 09:52:32 +0200 Subject: [PATCH 035/169] fix expression: inselect is only usable internally bzr revid: christophe@tinyerp.com-20080818075232-or6i63wafstw214w --- bin/osv/expression.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bin/osv/expression.py b/bin/osv/expression.py index b98c6fac9fc..c384d74a833 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -9,17 +9,18 @@ class expression(object): parse a domain expression use a real polish notation leafs are still in a ('foo', '=', 'bar') format - For more info: http://... + For more info: http://christophe-simonis-at-tiny.blogspot.com/2008/08/new-new-domain-notation.html """ def _is_operator(self, element): return isinstance(element, str) \ and element in ['&', '|', '!'] - def _is_leaf(self, element): + def _is_leaf(self, element, internal=False): return (isinstance(element, tuple) or isinstance(element, list)) \ and len(element) == 3 \ - and element[1] in ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of', 'inselect') + and element[1] in ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of') + and ((not internal) or element[1] in ('inselect',)) def __execute_recursive_in(self, cr, s, f, w, ids): res = [] @@ -266,7 +267,7 @@ class expression(object): stack = [] params = [] for i, e in reverse_enumerate(self.__exp): - if self._is_leaf(e): + if self._is_leaf(e, internal=True): table = self.__tables.has_key(i) and self.__tables[i] or self.__main_table q, p = self.__leaf_to_sql(e, table) params.insert(0, p) From 4a72330cc655621bc1ea4f4ebde7f57b74b35b18 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Mon, 18 Aug 2008 09:56:02 +0200 Subject: [PATCH 036/169] reverse_enumerate is prettier bzr revid: christophe@tinyerp.com-20080818075602-m9izdakbstgc2qq2 --- bin/tools/misc.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/bin/tools/misc.py b/bin/tools/misc.py index d905be92d42..f82a44d8808 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -290,22 +290,22 @@ def flatten(list): r.append(e) return r -reverse_enumerate = lambda l: izip(xrange(len(l)-1, -1, -1), reversed(l)) -reverse_enumerate.__doc__ = \ -"""Like enumerate but in the other sens ->>> a = ['a', 'b', 'c'] ->>> it = reverse_enumerate(a) ->>> it.next() -(2, 'c') ->>> it.next() -(1, 'b') ->>> it.next() -(0, 'a') ->>> it.next() -Traceback (most recent call last): - File "", line 1, in -StopIteration -""" +def reverse_enumerate(l): + """Like enumerate but in the other sens + >>> a = ['a', 'b', 'c'] + >>> it = reverse_enumerate(a) + >>> it.next() + (2, 'c') + >>> it.next() + (1, 'b') + >>> it.next() + (0, 'a') + >>> it.next() + Traceback (most recent call last): + File "", line 1, in + StopIteration + """ + return izip(xrange(len(l)-1, -1, -1), reversed(l)) #---------------------------------------------------------- # Emails From 0c80bf9a29d8df4d20d747578c58c7f439b6b7e9 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 18 Aug 2008 13:15:57 +0200 Subject: [PATCH 037/169] Bugfix for multi-companies computation of orderpoints bzr revid: fp@tinyerp.com-20080818111557-khe4fw0f2cg5os38 --- addons/mrp/wizard/schedulers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/mrp/wizard/schedulers.py b/addons/mrp/wizard/schedulers.py index 0f83bc08afc..e7b4ade90ae 100644 --- a/addons/mrp/wizard/schedulers.py +++ b/addons/mrp/wizard/schedulers.py @@ -47,8 +47,7 @@ def _procure_confirm(self, cr, uid, schedule_cycle=1.0, po_cycle=1.0,\ cr = pooler.get_db(use_new_cursor).cursor() wf_service = netsvc.LocalService("workflow") - cr.execute('select id from mrp_procurement where state=%s order by date_planned', ('exception',)) - ids = map(lambda x:x[0], cr.fetchall()) + ids=orderpoint_obj.search(cr,uid,[("active","=",True)],offset=offset,limit=100) for id in ids: wf_service.trg_validate(uid, 'mrp.procurement', id, 'button_restart', cr) if use_new_cursor: From f61d2ee3b08e4666cbf4587e589601f8b3427e65 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 18 Aug 2008 13:17:22 +0200 Subject: [PATCH 038/169] Better Syntax bzr revid: fp@tinyerp.com-20080818111722-h4p85v4djxvq99by --- addons/mrp/wizard/schedulers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mrp/wizard/schedulers.py b/addons/mrp/wizard/schedulers.py index e7b4ade90ae..b032234c552 100644 --- a/addons/mrp/wizard/schedulers.py +++ b/addons/mrp/wizard/schedulers.py @@ -47,7 +47,7 @@ def _procure_confirm(self, cr, uid, schedule_cycle=1.0, po_cycle=1.0,\ cr = pooler.get_db(use_new_cursor).cursor() wf_service = netsvc.LocalService("workflow") - ids=orderpoint_obj.search(cr,uid,[("active","=",True)],offset=offset,limit=100) + ids=orderpoint_obj.search(cr,uid,[],offset=offset,limit=100) for id in ids: wf_service.trg_validate(uid, 'mrp.procurement', id, 'button_restart', cr) if use_new_cursor: From 77bf737ff6562568119a837f99d9ec13093c60ab Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 18 Aug 2008 13:49:57 +0200 Subject: [PATCH 039/169] Bugfix in Expression bzr revid: fp@tinyerp.com-20080818114957-kb2f0qv9rf6c0nhu --- bin/osv/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/osv/expression.py b/bin/osv/expression.py index c384d74a833..7eb59970d66 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -19,7 +19,7 @@ class expression(object): def _is_leaf(self, element, internal=False): return (isinstance(element, tuple) or isinstance(element, list)) \ and len(element) == 3 \ - and element[1] in ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of') + and element[1] in ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of') \ and ((not internal) or element[1] in ('inselect',)) def __execute_recursive_in(self, cr, s, f, w, ids): From 3242b0cfee21e9a9a59ba8d8e3fc6e0598543995 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Mon, 18 Aug 2008 14:13:49 +0200 Subject: [PATCH 040/169] bugfix with internal leafs of expressions bzr revid: christophe@tinyerp.com-20080818121349-8jmcb03k5s1wyk8d --- bin/osv/expression.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/osv/expression.py b/bin/osv/expression.py index 7eb59970d66..9edf947a6ba 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -17,10 +17,12 @@ class expression(object): and element in ['&', '|', '!'] def _is_leaf(self, element, internal=False): + OPS = ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', '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 \ - and element[1] in ('=', '!=', '<>', '<=', '<', '>', '>=', '=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of') \ - and ((not internal) or element[1] in ('inselect',)) + and (((not internal) and element[1] in OPS) \ + or (internal and element[1] in INTERNAL_OPS)) def __execute_recursive_in(self, cr, s, f, w, ids): res = [] @@ -268,7 +270,7 @@ class expression(object): params = [] for i, e in reverse_enumerate(self.__exp): if self._is_leaf(e, internal=True): - table = self.__tables.has_key(i) and self.__tables[i] or self.__main_table + table = self.__tables.get(i, self.__main_table) q, p = self.__leaf_to_sql(e, table) params.insert(0, p) stack.append(q) From 2f6e14b151fbc9c57053217e20691e644d8758b7 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Mon, 18 Aug 2008 14:58:00 +0200 Subject: [PATCH 041/169] expression: bugfix with many2many fields bzr revid: christophe@tinyerp.com-20080818125800-tbv8obinijtgrasd --- bin/osv/expression.py | 8 +++++--- bin/osv/orm.py | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/osv/expression.py b/bin/osv/expression.py index 9edf947a6ba..bf6b8f9fe50 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -144,8 +144,10 @@ class expression(object): if field_obj == table: return ids return self.__execute_recursive_in(cr, field._id1, field._rel, field._id2, ids) - - self.__exp[i] = ('id', 'in', _rec_convert(ids2 + _rec_get(ids2, field_obj, working_table._parent_name))) + + dom = _rec_get(ids2, field_obj, working_table._parent_name) + ids2 = field_obj.search(cr, uid, doms, context=context) + self.__exp[i] = ('id', 'in', _rec_convert(ids2)) else: if isinstance(right, basestring): res_ids = [x[0] for x in field_obj.name_search(cr, uid, right, [], operator)] @@ -161,7 +163,7 @@ class expression(object): self.__operator = 'in' if field._obj != working_table._name: - dom = _rec_get(ids2, field_obj, working_table._parent_name, left=left, prefix=left+'.') + dom = _rec_get(ids2, field_obj, working_table._parent_name,) else: dom = _rec_get(ids2, working_table, left) self.__exp = self.__exp[:i] + dom + self.__exp[i+1:] diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 8cfa2599472..e5110693dc9 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -2333,6 +2333,8 @@ class orm(orm_template): # compute the where, order by, limit and offset clauses (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context) + print "self: %r\nqu1: %r\nqu2: %r\ntables: %r\n" % (self, qu1, qu2, tables,) + if len(qu1): qu1 = ' where '+string.join(qu1, ' and ') else: From 187455a66696880dd942ad83e98fe1cf97219f93 Mon Sep 17 00:00:00 2001 From: "hda@tinyerp.com" <> Date: Mon, 18 Aug 2008 19:12:02 +0530 Subject: [PATCH 042/169] add editable attribute in field bzr revid: hda@tinyerp.com-20080818134202-ud8gwwkcnyiz2pb0 --- bin/addons/base/rng/view.rng | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/addons/base/rng/view.rng b/bin/addons/base/rng/view.rng index d9fc926f204..2fdf22bec05 100644 --- a/bin/addons/base/rng/view.rng +++ b/bin/addons/base/rng/view.rng @@ -121,6 +121,7 @@ + From 1098ae60915bfa225515dfdd87696ee11599c9ba Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 18 Aug 2008 22:19:46 +0200 Subject: [PATCH 043/169] Improved menu name for requests bzr revid: fp@tinyerp.com-20080818201946-m88ilwjf650ng4y0 --- bin/addons/base/res/res_request_view.xml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bin/addons/base/res/res_request_view.xml b/bin/addons/base/res/res_request_view.xml index 021a88557a9..a3d8809fdae 100644 --- a/bin/addons/base/res/res_request_view.xml +++ b/bin/addons/base/res/res_request_view.xml @@ -60,8 +60,7 @@ Requests ir.actions.act_window res.request - form - + @@ -90,11 +89,11 @@ - Request + Accepted Links in Requests ir.actions.act_window res.request.link form - + @@ -128,4 +127,4 @@ - \ No newline at end of file + From b9b673502d1e1a4e2884bb32ab2295fc4266cfa3 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 18 Aug 2008 23:07:39 +0200 Subject: [PATCH 044/169] Speed Improvement bzr revid: fp@tinyerp.com-20080818210739-ugfuzb1mny8u0fh3 --- bin/osv/orm.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index e5110693dc9..8cfa2599472 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -2333,8 +2333,6 @@ class orm(orm_template): # compute the where, order by, limit and offset clauses (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context) - print "self: %r\nqu1: %r\nqu2: %r\ntables: %r\n" % (self, qu1, qu2, tables,) - if len(qu1): qu1 = ' where '+string.join(qu1, ' and ') else: From 9ab39c25da2dff031aae8a8505be9ce3774c5932 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 18 Aug 2008 23:08:08 +0200 Subject: [PATCH 045/169] Speed Improvement: accounting bzr revid: fp@tinyerp.com-20080818210808-01v66l4mnwef6umw --- addons/account/account.py | 1 - addons/account/partner.py | 43 +++++++++++++++++++++++++-------------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/addons/account/account.py b/addons/account/account.py index d9fba92ef14..9da50f7564a 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -195,7 +195,6 @@ class account_account(osv.osv): for i in ids2: for a in range(len(field_names)): res[id][a] += accounts.get(i, (0.0,0.0,0.0))[a] - print res return res def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}): diff --git a/addons/account/partner.py b/addons/account/partner.py index 06df0ed0c57..b6bcb2324b2 100644 --- a/addons/account/partner.py +++ b/addons/account/partner.py @@ -35,20 +35,33 @@ class res_partner(osv.osv): _name = 'res.partner' _inherit = 'res.partner' _description = 'Partner' - def _credit_get(self, cr, uid, ids, name, arg, context): - res={} + def _credit_debit_get(self, cr, uid, ids, field_names, arg, context): query = self.pool.get('account.move.line')._query_get(cr, uid, context=context) - for id in ids: - cr.execute("select sum(debit-credit) from account_move_line as l where account_id in (select id from account_account where type = %s and active) and partner_id=%d and reconcile_id is null and "+query, ('receivable', id)) - res[id]=cr.fetchone()[0] or 0.0 - return res - - def _debit_get(self, cr, uid, ids, name, arg, context): - res={} - query = self.pool.get('account.move.line')._query_get(cr, uid, context=context) - for id in ids: - cr.execute("select sum(debit-credit) from account_move_line as l where account_id in (select id from account_account where type = %s and active) and partner_id=%d and reconcile_id is null and "+query, ('payable', id)) - res[id]=cr.fetchone()[0] or 0.0 + cr.execute(("""select + l.partner_id, a.type, sum(l.debit-l.credit) + from + account_move_line l + left join + account_account a on (l.account_id=a.id) + where + a.type in ('receivable','payable') and + l.partner_id in (%s) and + l.reconcile_id is null and + """ % (','.join(map(str, ids)),))+query+""" + group by + l.partner_id, a.type + """) + tinvert = { + 'credit': 'receivable', + 'debit': 'payable' + } + maps = {} + for i in range(len(field_names)): + maps[{'credit': 'receivable', 'debit': 'payable' }[field_names[i]]] = i + res = {}.fromkeys(ids, map(lambda x: 0.0, field_names)) + for pid,type,val in cr.fetchall(): + if type in maps: + res[pid][maps[type]] = val return res def _credit_search(self, cr, uid, obj, name, args): @@ -74,8 +87,8 @@ class res_partner(osv.osv): return [('id','in',map(lambda x:x[0], res))] _columns = { - 'credit': fields.function(_credit_get, fnct_search=_credit_search, method=True, string='Total Receivable'), - 'debit': fields.function(_debit_get, fnct_search=_debit_search, method=True, string='Total Payable'), + 'credit': fields.function(_credit_debit_get, fnct_search=_credit_search, method=True, string='Total Receivable', multi='dc'), + 'debit': fields.function(_credit_debit_get, fnct_search=_debit_search, method=True, string='Total Payable', multi='dc'), 'debit_limit': fields.float('Payable Limit'), 'property_account_payable': fields.property( 'account.account', From 1c00f109119f2aedb5cafbf9323f2a605ea93522 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 00:19:04 +0200 Subject: [PATCH 046/169] Adding a clause to not read inherit views in fields_view_get bzr revid: fp@tinyerp.com-20080818221904-s3ssm41w8v1hykj6 --- bin/osv/orm.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 8cfa2599472..5c312503c4f 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -968,7 +968,15 @@ class orm_template(object): where = (model and (" and model='%s'" % (self._name,))) or '' cr.execute('SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE id=%d'+where, (view_id,)) else: - cr.execute('SELECT arch,name,field_parent,id,type,inherit_id FROM ir_ui_view WHERE model=%s AND type=%s ORDER BY priority', (self._name, view_type)) + cr.execute('''SELECT + arch,name,field_parent,id,type,inherit_id + FROM + ir_ui_view + WHERE + model=%s AND + type=%s AND + inherit_id IS NULL + ORDER BY priority''', (self._name, view_type)) sql_res = cr.fetchone() if not sql_res: break From bd7c09b70bf14b351448af288406ff93bc3bf110 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 00:26:00 +0200 Subject: [PATCH 047/169] Small bugfixes: ondelete=cascade bzr revid: fp@tinyerp.com-20080818222600-2cmw4rvatzp5qorv --- bin/addons/base/ir/ir_ui_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/addons/base/ir/ir_ui_view.py b/bin/addons/base/ir/ir_ui_view.py index 309841a7ac6..84442427145 100644 --- a/bin/addons/base/ir/ir_ui_view.py +++ b/bin/addons/base/ir/ir_ui_view.py @@ -67,7 +67,7 @@ class view(osv.osv): ('graph', 'Graph'), ('calendar', 'Calendar')), 'View Type', required=True), 'arch': fields.text('View Architecture', required=True), - 'inherit_id': fields.many2one('ir.ui.view', 'Inherited View'), + 'inherit_id': fields.many2one('ir.ui.view', 'Inherited View', ondelete='cascade'), 'field_parent': fields.char('Childs Field',size=64), } _defaults = { From 7f9e392861fa7fefa78791354b420ae6c45b8b3e Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 00:31:40 +0200 Subject: [PATCH 048/169] Check for globals in socket bzr revid: fp@tinyerp.com-20080818223140-l69ekd9uyfw2c87w --- bin/tiny_socket.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/tiny_socket.py b/bin/tiny_socket.py index 076155748d3..8f89fe97297 100644 --- a/bin/tiny_socket.py +++ b/bin/tiny_socket.py @@ -28,6 +28,7 @@ ############################################################################### import socket import cPickle +import cStringIO import marshal class Myexception(Exception): @@ -82,7 +83,11 @@ class mysocket: if chunk == '': raise RuntimeError, "socket connection broken" msg = msg + chunk - res = cPickle.loads(msg) + msgio = cStringIO.StringIO(msg) + unpickler = cPickle.Unpickler(msgio) + unpickler.find_global = None + res = unpickler.load() + if isinstance(res[0],Exception): if exception: raise Myexception(str(res[0]), str(res[1])) From 0f12b770c5b0b0ac15442dae3ddf829f8082625a Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 00:34:22 +0200 Subject: [PATCH 049/169] Very small speed improvement at db creation bzr revid: fp@tinyerp.com-20080818223422-yiuv0dgr23vrjhfe --- bin/addons/base/ir/ir_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/addons/base/ir/ir_model.py b/bin/addons/base/ir/ir_model.py index da4b192aaa2..499a9a4354b 100644 --- a/bin/addons/base/ir/ir_model.py +++ b/bin/addons/base/ir/ir_model.py @@ -147,8 +147,8 @@ class ir_model_fields(osv.osv): def create(self, cr, user, vals, context=None): if 'model_id' in vals: - model_data=self.pool.get('ir.model').read(cr,user,vals['model_id']) - vals['model']=model_data['model'] + model_data=self.pool.get('ir.model').browse(cr,user,vals['model_id']) + 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) From 49553c4340cb1b68cb6cf3e8ee12b0b369057009 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 08:59:31 +0200 Subject: [PATCH 050/169] Modified bzr revid: fp@tinyerp.com-20080819065931-78c6pg5kopg0gjkv --- addons/account/account.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addons/account/account.py b/addons/account/account.py index 9da50f7564a..729e2b89fdd 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -195,6 +195,9 @@ class account_account(osv.osv): for i in ids2: for a in range(len(field_names)): res[id][a] += accounts.get(i, (0.0,0.0,0.0))[a] +# TODO: if account.type is consolidation: compute all childs like before + +# currency conversion + return res def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}): From f873f8761cd46cbda76840a49aab43f4516cfe72 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 09:09:41 +0200 Subject: [PATCH 051/169] Removed Fast Chart of Accounts _order = parent_left bzr revid: fp@tinyerp.com-20080819070941-i15jwyp36gh8hg7n --- addons/account/account.py | 3 ++- addons/account/account_view.xml | 21 --------------------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/addons/account/account.py b/addons/account/account.py index 729e2b89fdd..5855326d4da 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -129,7 +129,8 @@ class account_tax(osv.osv): account_tax() class account_account(osv.osv): - _order = "code" + _order = "parent_left" + _parent_order = "code" _name = "account.account" _description = "Account" _parent_store = True diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index 1b50734c4b6..d3b1b47c12b 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -172,27 +172,6 @@ - - account.account.list - account.account - tree - child_id - - - - - - - - - - Fast Chart of Accounts - account.account - tree - - [('code','=','0')] - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + @@ -1080,8 +1091,22 @@ ir.actions.server form + {'key':'myval'} - + + + ir.model.fields.tree + ir.model.fields + tree + + + + + + + + + diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index 159707cdd1d..324d85381c7 100755 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -255,10 +255,6 @@ class act_url(osv.osv): } act_url() -def _get_fields(self, cr, uid, context={}): - - return [('key','Email Value')] - def model_get(self, cr, uid, context={}): wkf_pool = self.pool.get('workflow') ids = wkf_pool.search(cr, uid, []) @@ -273,7 +269,62 @@ def model_get(self, cr, uid, context={}): res.append((model, name)) return res - + +class ir_model_fields(osv.osv): + _inherit = 'ir.model.fields' + _rec_name = 'complete_name' + _columns = { + 'complete_name': fields.char('Complete Name', required=True, size=64, select=1), + } + + def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=80): + + print name, args, operator, context + + def get_fields(cr, uid, field, rel): + result = [] + mobj = self.pool.get('ir.model') + id = mobj.search(cr, uid, [('model','=',rel)]) + + obj = self.pool.get('ir.model.fields') + ids = obj.search(cr, uid, [('model_id','in',id)]) + records = obj.read(cr, uid, ids) + for record in records: + id = record['id'] + fld = field + '/' + record['name'] + + result.append((id, fld)) + return result + + if not args: + args=[] + if not context: + context={} + return super(ir_model_fields, self).name_search(cr, uid, name, args, operator, context, limit) + + result = [] + obj = self.pool.get('ir.model.fields') + ids = obj.search(cr, uid, args) + records = obj.read(cr, uid, ids) + for record in records: + id = record['id'] + field = record['name'] + + if record['ttype'] == 'many2one': + rel = record['relation'] + res = get_fields(cr, uid, field, record['relation']) + for rs in res: + result.append(rs) + + result.append((id, field)) + + for rs in result: + obj.write(cr, uid, [rs[0]], {'complete_name':rs[1]}) + + #result = super(ir_model_fields, self).name_search(cr, uid, name, [['complete_name','ilike',name]], operator, context, limit) + return result + +ir_model_fields() # # Actions that are run on the server side # @@ -302,7 +353,8 @@ class actions_server(osv.osv): #'trigger_object': fields.char('Trigger Object', size=128), #'trigger_object_id': fields.char('Trigger Object ID', size=128), 'message': fields.text('Message', translate=True), - 'address': fields.selection(_get_fields ,'Email / SMS', size=128), + 'address': fields.many2one('ir.model.fields', 'Email From / SMS'), + #selection(_get_fields ,'Email / SMS', size=128), 'child_ids': fields.one2many('ir.actions.actions', 'parent_id', 'Others Actions'), 'usage': fields.char('Action Usage', size=32), 'type': fields.char('Report Type', size=32, required=True), @@ -322,6 +374,11 @@ class actions_server(osv.osv): """ } +# def on_change_model(self, cr, uid, ids, model_id): +# print '**************** : method called' +# res = {'value' : {'address': [('key1','Email Value One')]} } +# return res + # # Context should contains: # ids : original ids @@ -344,9 +401,14 @@ class actions_server(osv.osv): exec action.code in localdict if 'action' in localdict: return localdict['action'] + if action.state == 'email': user = config['email_from'] subject = action.name + + #TODO : Apply Mail merge in to the Content of the Email + print '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ',context + body = action.message if tools.email_send_attach(user, action.address, subject, body, debug=False) == True: logger.notifyChannel('email', netsvc.LOG_INFO, 'Email successfully send to : %s' % (action.address)) @@ -361,10 +423,12 @@ class actions_server(osv.osv): wf_service.trg_validate(uid, model, int(id), action.trigger_name, cr) if action.state == 'sms': + #TODO: Apply mearge with the field if tools.sms_send(user, password, api_id, text, to) == True: logger.notifyChannel('sms', netsvc.LOG_INFO, 'SMS successfully send to : %s' % (action.address)) else: logger.notifyChannel('sms', netsvc.LOG_ERROR, 'Failed to send SMS to : %s' % (action.address)) + if action.state == 'other': localdict = { 'self': self.pool.get(action.model_id.model), @@ -376,15 +440,10 @@ class actions_server(osv.osv): } for act in action.child_ids: - code = """ - action = { - 'res_model': %s, - 'type': %s, - 'name': %s, - 'usage': %s, - } - """ % (action.model_id.model, act.type, act.name, act.usage) - exec action.code in localdict + code = """action = {'model':'%s','type':'%s', %s}""" % (action.model_id.model, act.type, act.usage) + exec code in localdict + if 'action' in localdict: + return localdict['action'] return False actions_server() From 9c91996f6744c3968fe6f05445b2411516c57189 Mon Sep 17 00:00:00 2001 From: qdp Date: Tue, 19 Aug 2008 14:13:45 +0200 Subject: [PATCH 064/169] *modified -C option to make partial import bzr revid: qdp@tinyerp.com-20080819121345-up2us3o4o1g4v8us --- bin/tools/config.py | 6 +++--- bin/tools/convert.py | 12 +++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/bin/tools/config.py b/bin/tools/config.py index f4a3674e67e..99f972ac342 100644 --- a/bin/tools/config.py +++ b/bin/tools/config.py @@ -58,7 +58,7 @@ class configmanager(object): 'addons_path': None, 'root_path': None, 'debug_mode': False, - 'commit_mode': False, + 'import_partial': "", 'pidfile': None, 'logfile': None, 'secure': False, @@ -114,7 +114,7 @@ class configmanager(object): group.add_option("--db_host", dest="db_host", help="specify the database host") group.add_option("--db_port", dest="db_port", help="specify the database port") group.add_option("--db_maxconn", dest="db_maxconn", default='64', help="specify the the maximum number of physical connections to posgresql") - group.add_option("-C", "--commit-mode", dest="commit_mode", action="store_true", help="Several commit during one file importation. Use this for big data importation.", default=False) + group.add_option("-C", "--commit-mode", dest="import_partial", help="Several commit during one file importation. Use this for big data importation. Provide a filename to store intermediate state.", default=False) parser.add_option_group(group) group = optparse.OptionGroup(parser, "Internationalisation options", @@ -153,7 +153,7 @@ class configmanager(object): self.options['pidfile'] = False for arg in ('interface', 'port', 'db_name', 'db_user', 'db_password', 'db_host', - 'db_port', 'logfile', 'pidfile', 'secure', 'smtp_ssl', 'email_from', 'smtp_server', 'smtp_user', 'smtp_password', 'price_accuracy', 'netinterface', 'netport', 'db_maxconn', 'commit_mode', 'addons_path'): + 'db_port', 'logfile', 'pidfile', 'secure', 'smtp_ssl', 'email_from', 'smtp_server', 'smtp_user', 'smtp_password', 'price_accuracy', 'netinterface', 'netport', 'db_maxconn', 'import_partial', 'addons_path'): if getattr(opt, arg): self.options[arg] = getattr(opt, arg) diff --git a/bin/tools/convert.py b/bin/tools/convert.py index bca44cb349f..178df2b7bc6 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -657,7 +657,7 @@ form: module.record_id""" % (xml_id,) id = self.pool.get('ir.model.data')._update(cr, self.uid, rec_model, self.module, res, rec_id or False, not self.isnoupdate(data_node), noupdate=self.isnoupdate(data_node), mode=self.mode ) if rec_id: self.idref[rec_id] = int(id) - if config.get('commit_mode', False): + if config.get('import_partial', False): cr.commit() return rec_model, id @@ -724,6 +724,12 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', pool = pooler.get_pool(cr.dbname) + import pickle + if config.get('import_partial'): + data = pickle.load(config.get('import_partial')) + if fname in data: + if not data[fname]: + return input = StringIO.StringIO(csvcontent) reader = csv.reader(input, quotechar='"', delimiter=',') fields = reader.next() @@ -738,6 +744,10 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', continue datas.append( map(lambda x:x.decode('utf8').encode('utf8'), line)) pool.get(model).import_data(cr, uid, fields, datas,mode, module,noupdate) + if config.get('import_partial'): + data = pickle.load(config.get('import_partial')) + data[fname] = 0 + pickle.save(config.get('import_partial'), data) # # xml import/export From 5d2bc5159116c5ec483d762c04db30583f751445 Mon Sep 17 00:00:00 2001 From: "hda@tinyerp.com" <> Date: Tue, 19 Aug 2008 17:50:16 +0530 Subject: [PATCH 065/169] bugfix for fields and groups bzr revid: hda@tinyerp.com-20080819122016-63cier1nkqrvixbg --- bin/addons/base/rng/view.rng | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/addons/base/rng/view.rng b/bin/addons/base/rng/view.rng index 5eefd89e976..666ab76194b 100644 --- a/bin/addons/base/rng/view.rng +++ b/bin/addons/base/rng/view.rng @@ -6,6 +6,7 @@ + @@ -16,6 +17,7 @@ + @@ -30,10 +32,13 @@ + + + From 8da94d89f86b807edf7079601860b1328294ba02 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 14:30:13 +0200 Subject: [PATCH 066/169] Bugfix bzr revid: fp@tinyerp.com-20080819123013-fk1bhqym921tg2wp --- bin/addons/base/ir/ir_actions.py | 107 +++++++++++++++---------------- bin/addons/base/ir/ir_model.py | 1 + bin/osv/osv.py | 5 +- bin/tools/convert.py | 1 + 4 files changed, 58 insertions(+), 56 deletions(-) diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index 324d85381c7..9ed4565169a 100755 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -270,62 +270,59 @@ def model_get(self, cr, uid, context={}): return res -class ir_model_fields(osv.osv): - _inherit = 'ir.model.fields' - _rec_name = 'complete_name' - _columns = { - 'complete_name': fields.char('Complete Name', required=True, size=64, select=1), - } - - def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=80): - - print name, args, operator, context - - def get_fields(cr, uid, field, rel): - result = [] - mobj = self.pool.get('ir.model') - id = mobj.search(cr, uid, [('model','=',rel)]) - - obj = self.pool.get('ir.model.fields') - ids = obj.search(cr, uid, [('model_id','in',id)]) - records = obj.read(cr, uid, ids) - for record in records: - id = record['id'] - fld = field + '/' + record['name'] - - result.append((id, fld)) - return result - - if not args: - args=[] - if not context: - context={} - return super(ir_model_fields, self).name_search(cr, uid, name, args, operator, context, limit) - - result = [] - obj = self.pool.get('ir.model.fields') - ids = obj.search(cr, uid, args) - records = obj.read(cr, uid, ids) - for record in records: - id = record['id'] - field = record['name'] - - if record['ttype'] == 'many2one': - rel = record['relation'] - res = get_fields(cr, uid, field, record['relation']) - for rs in res: - result.append(rs) - - result.append((id, field)) - - for rs in result: - obj.write(cr, uid, [rs[0]], {'complete_name':rs[1]}) - - #result = super(ir_model_fields, self).name_search(cr, uid, name, [['complete_name','ilike',name]], operator, context, limit) - return result - -ir_model_fields() +#class ir_model_fields(osv.osv): +# _inherit = 'ir.model.fields' +# _rec_name = 'complete_name' +# _columns = { +# 'complete_name': fields.char('Complete Name', required=True, size=64, select=1), +# } # +# def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=80): +# def get_fields(cr, uid, field, rel): +# result = [] +# mobj = self.pool.get('ir.model') +# id = mobj.search(cr, uid, [('model','=',rel)]) +# +# obj = self.pool.get('ir.model.fields') +# ids = obj.search(cr, uid, [('model_id','in',id)]) +# records = obj.read(cr, uid, ids) +# for record in records: +# id = record['id'] +# fld = field + '/' + record['name'] +# +# result.append((id, fld)) +# return result +# +# if not args: +# args=[] +# if not context: +# context={} +# return super(ir_model_fields, self).name_search(cr, uid, name, args, operator, context, limit) +# +# result = [] +# obj = self.pool.get('ir.model.fields') +# ids = obj.search(cr, uid, args) +# records = obj.read(cr, uid, ids) +# for record in records: +# id = record['id'] +# field = record['name'] +# +# if record['ttype'] == 'many2one': +# rel = record['relation'] +# res = get_fields(cr, uid, field, record['relation']) +# for rs in res: +# result.append(rs) +# +# result.append((id, field)) +# +# for rs in result: +# obj.write(cr, uid, [rs[0]], {'complete_name':rs[1]}) +# +# #result = super(ir_model_fields, self).name_search(cr, uid, name, [['complete_name','ilike',name]], operator, context, limit) +# return result +# +#ir_model_fields() +## # Actions that are run on the server side # class actions_server(osv.osv): diff --git a/bin/addons/base/ir/ir_model.py b/bin/addons/base/ir/ir_model.py index 499a9a4354b..e811c25ef7d 100644 --- a/bin/addons/base/ir/ir_model.py +++ b/bin/addons/base/ir/ir_model.py @@ -122,6 +122,7 @@ class ir_model_fields(osv.osv): 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'), 'group_name': fields.char('Group Name', size=128), 'view_load': fields.boolean('View Auto-Load'), + 'complete_name': fields.char('Complete Name', required=True, size=64, select=1), } _defaults = { 'relate': lambda *a: 0, diff --git a/bin/osv/osv.py b/bin/osv/osv.py index 8e2944eed76..611ba504955 100644 --- a/bin/osv/osv.py +++ b/bin/osv/osv.py @@ -236,7 +236,10 @@ class osv(orm.orm): parent_name = hasattr(cls, '_inherit') and cls._inherit if parent_name: parent_class = pool.get(parent_name).__class__ - assert parent_class, "parent class %s does not exist !" % parent_name + + print cls + print cls._name + assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module) nattr = {} for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'): new = copy.copy(getattr(pool.get(parent_name), s)) diff --git a/bin/tools/convert.py b/bin/tools/convert.py index 178df2b7bc6..7ad2e9d93db 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -730,6 +730,7 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', if fname in data: if not data[fname]: return + input = StringIO.StringIO(csvcontent) reader = csv.reader(input, quotechar='"', delimiter=',') fields = reader.next() From 84c2b58ea3d5283b3bfafc7e9147b0440641d88f Mon Sep 17 00:00:00 2001 From: Harshad Modi Date: Tue, 19 Aug 2008 18:16:16 +0530 Subject: [PATCH 067/169] fixed bugs on installing module:\n *raising an error when a module to be installed is unknown or uninstalled of the system and telling which module is missing. bzr revid: hmo@tinyerp.com-20080819124616-35yndpzazw3gvo98 --- bin/addons/base/module/wizard/wizard_module_upgrade.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bin/addons/base/module/wizard/wizard_module_upgrade.py b/bin/addons/base/module/wizard/wizard_module_upgrade.py index c9cf6042108..55432bd44f9 100644 --- a/bin/addons/base/module/wizard/wizard_module_upgrade.py +++ b/bin/addons/base/module/wizard/wizard_module_upgrade.py @@ -89,6 +89,15 @@ class wizard_info_get(wizard.interface): mod_obj = pool.get('ir.module.module') ids = mod_obj.search(cr, uid, [ ('state', 'in', ['to upgrade', 'to remove', 'to install'])]) + unmet_packages=[] + mod_dep_obj = pool.get('ir.module.module.dependency') + for mod in mod_obj.browse(cr,uid,ids): + depends_mod_ids=mod_dep_obj.search(cr,uid,[('module_id','=',mod.id)]) + for dep_mod in mod_dep_obj.browse(cr,uid,depends_mod_ids): + if dep_mod.state in ('unknown','uninstalled'): + unmet_packages.append(dep_mod.name) + if len(unmet_packages): + raise wizard.except_wizard('Unmet dependency !','Following modules are uninstalled or unknown. \n\n'+'\n'.join(unmet_packages)) mod_obj.download(cr, uid, ids, context=context) cr.commit() db, pool = pooler.restart_pool(cr.dbname, update_module=True) From 83b19dbcec0b597e098e9fbe808f93fbf4b01903 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 15:03:11 +0200 Subject: [PATCH 068/169] Support for Partial Commit bzr revid: fp@tinyerp.com-20080819130311-bzlschcnywx2voot --- bin/addons/__init__.py | 3 +++ bin/osv/orm.py | 42 +++++++++++++++++++++--------------------- bin/tools/convert.py | 8 +++++--- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index 74ecc9a6f04..ec45981081b 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -327,6 +327,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False): if force_demo: force.append('demo') if update_module: + for module in tools.config['init']: + cr.execute('update ir_module_module set state=%s where state=%s and name=%s', ('to install', 'uninstalled', module)) + cr.commit() cr.execute("select name from ir_module_module where state in ('installed', 'to install', 'to upgrade','to remove')") else: cr.execute("select name from ir_module_module where state in ('installed', 'to upgrade', 'to remove')") diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 5c312503c4f..198a3a17407 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -593,28 +593,28 @@ class orm_template(object): while len(datas): res = {} - try: - (res, other, warning, translate, data_id) = \ - process_liness(self, datas, [], fields_def) - if warning: - cr.rollback() - return (-1, res, warning, '') - id = self.pool.get('ir.model.data')._update(cr, uid, self._name, - current_module, res, xml_id=data_id, mode=mode, - noupdate=noupdate) - for lang in translate: - context2 = context.copy() - context2['lang'] = lang - self.write(cr, uid, [id], translate[lang], context2) - if config.get('commit_mode', False): - cr.commit() - except Exception, e: - logger.notifyChannel("import", netsvc.LOG_ERROR, e) + #try: + (res, other, warning, translate, data_id) = \ + process_liness(self, datas, [], fields_def) + if warning: cr.rollback() - try: - return (-1, res, e[0], warning) - except: - return (-1, res, e[0], '') + return (-1, res, warning, '') + id = self.pool.get('ir.model.data')._update(cr, uid, self._name, + current_module, res, xml_id=data_id, mode=mode, + noupdate=noupdate) + for lang in translate: + context2 = context.copy() + context2['lang'] = lang + self.write(cr, uid, [id], translate[lang], context2) + if config.get('import_partial', False): + cr.commit() + #except Exception, e: + # logger.notifyChannel("import", netsvc.LOG_ERROR, e) + # cr.rollback() + # try: + # return (-1, res, e[0], warning) + # except: + # return (-1, res, e[0], '') done += 1 # # TODO: Send a request with the result and multi-thread ! diff --git a/bin/tools/convert.py b/bin/tools/convert.py index 7ad2e9d93db..626052c060d 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -726,7 +726,9 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', import pickle if config.get('import_partial'): - data = pickle.load(config.get('import_partial')) + if not os.path.isfile(config.get('import_partial')): + pickle.dump({}, file(config.get('import_partial'),'w+')) + data = pickle.load(file(config.get('import_partial'))) if fname in data: if not data[fname]: return @@ -746,9 +748,9 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', datas.append( map(lambda x:x.decode('utf8').encode('utf8'), line)) pool.get(model).import_data(cr, uid, fields, datas,mode, module,noupdate) if config.get('import_partial'): - data = pickle.load(config.get('import_partial')) + data = pickle.load(file(config.get('import_partial'))) data[fname] = 0 - pickle.save(config.get('import_partial'), data) + pickle.dump(data, file(config.get('import_partial'),'wb')) # # xml import/export From c4c6bea2d17369ea28955f5492edf1c9cb508872 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Tue, 19 Aug 2008 15:10:16 +0200 Subject: [PATCH 069/169] * allow export of translation po files into a tgz archive. * allow export of a new language from the command line bzr revid: christophe@tinyerp.com-20080819131016-nghtt1x274evlwi4 --- .../base/module/wizard/wizard_export_lang.py | 81 +++---------------- bin/tinyerp-server.py | 6 +- bin/tools/config.py | 13 +-- bin/tools/translate.py | 52 +++++++++--- 4 files changed, 62 insertions(+), 90 deletions(-) diff --git a/bin/addons/base/module/wizard/wizard_export_lang.py b/bin/addons/base/module/wizard/wizard_export_lang.py index f5df07532df..dc288707577 100644 --- a/bin/addons/base/module/wizard/wizard_export_lang.py +++ b/bin/addons/base/module/wizard/wizard_export_lang.py @@ -37,81 +37,14 @@ import pooler from osv import fields,osv -''' -view_form_init=""" -
- - - - -
""" - -view_form_finish=""" -
- - - - - -
""" - -class wizard_export_lang(wizard.interface): - def _get_language(self, cr, uid, context): - lang_obj=pooler.get_pool(cr.dbname).get('res.lang') - ids=lang_obj.search(cr, uid, [('active', '=', True),]) - langs=lang_obj.browse(cr, uid, ids) - return [(lang.code, lang.translatable and lang.name or _('New language')) for lang in langs] - - def _get_file(self, cr, uid, data, context): - file=tools.trans_generate(data['form']['lang'], 'all', dbname=cr.dbname) - buf=StringIO.StringIO() - writer=csv.writer(buf, 'UNIX') - for row in file: - writer.writerow(row) - del file - out=base64.encodestring(buf.getvalue()) - buf.close() - return {'data': out} - - fields_form={ - 'lang': {'string':'Language', 'type':'selection', 'selection':_get_language,}, - } - fields_form_finish={ - 'data': {'string':'File', 'type':'binary', 'readonly': True,}, - } - states={ - 'init':{ - 'actions': [], - 'result': {'type': 'form', 'arch': view_form_init, 'fields': fields_form, - 'state': [ - ('end', 'Cancel', 'gtk-cancel'), - ('finish', 'Ok', 'gtk-ok', True) - ] - } - }, - 'finish':{ - 'actions': [_get_file], - 'result': {'type': 'form', 'arch': view_form_finish, - 'fields': fields_form_finish, - 'state': [ - ('end', 'Close', 'gtk-cancel', True) - ] - } - }, - } -wizard_export_lang('module.lang.export') -''' class wizard_export_lang(osv.osv_memory): def _get_languages(self, cr, uid, context): lang_obj=pooler.get_pool(cr.dbname).get('res.lang') - ids=lang_obj.search(cr, uid, [('active', '=', True),]) + ids=lang_obj.search(cr, uid, ['&', ('active', '=', True), ('translatable', '=', True),]) langs=lang_obj.browse(cr, uid, ids) - return [(lang.code, lang.translatable and lang.name or _('New language')) for lang in langs] + return [('', _('New language'))] + [(lang.code, lang.name) for lang in langs] def act_cancel(self, cr, uid, ids, context=None): @@ -132,7 +65,11 @@ class wizard_export_lang(osv.osv_memory): if this.format == 'csv': this.advice = _("Save this document to a .CSV file and open it with your favourite spreadsheet software. The file encoding is UTF-8. You have to translate the latest column before reimporting it.") elif this.format == 'po': - this.advice = _("Save this document to a .po file and edit it with a specific software or a text editor. The file encoding is UTF-8.") + ext = this.lang and '.po' or '.pot' + this.advice = _("Save this document to a %s file and edit it with a specific software or a text editor. The file encoding is UTF-8." % (ext,)) + elif this.format == 'tgz': + ext = this.lang and '.po' or '.pot' + this.advice = _('Save this document to a .tgz file. This archive containt UTF-8 %s files and may be uploaded to launchpad.' % (ext,)) out=base64.encodestring(buf.getvalue()) buf.close() @@ -140,8 +77,8 @@ class wizard_export_lang(osv.osv_memory): _name = "wizard.module.lang.export" _columns = { - 'lang': fields.selection(_get_languages, 'Language',required=True), - 'format': fields.selection( ( ('csv','CSV File'), ('po','PO File') ), 'File Format', required=True), + 'lang': fields.selection(_get_languages, 'Language'), # not required: unset = new language + 'format': fields.selection( ( ('csv','CSV File'), ('po','PO File'), ('tgz', 'TGZ Archive')), 'File Format', required=True), 'modules': fields.many2many('ir.module.module', 'rel_modules_langexport', 'wiz_id', 'module_id', 'Modules', domain=[('state','=','installed')]), 'data': fields.binary('File', readonly=True), 'advice': fields.text('', readonly=True), diff --git a/bin/tinyerp-server.py b/bin/tinyerp-server.py index 969cd507965..e5d9f2626b3 100755 --- a/bin/tinyerp-server.py +++ b/bin/tinyerp-server.py @@ -184,7 +184,11 @@ if tools.config['init'] or tools.config['update']: if tools.config["translate_out"]: import csv - logger.notifyChannel("init", netsvc.LOG_INFO, 'writing translation file for language %s to %s' % (tools.config["language"], tools.config["translate_out"])) + if tools.config["language"]: + msg = "language %s" % (tools.config["language"],) + else: + msg = "new language" + logger.notifyChannel("init", netsvc.LOG_INFO, 'writing translation file for %s to %s' % (msg, tools.config["translate_out"])) fileformat = os.path.splitext(tools.config["translate_out"])[-1][1:].lower() buf = file(tools.config["translate_out"], "w") diff --git a/bin/tools/config.py b/bin/tools/config.py index f4a3674e67e..49993b2a534 100644 --- a/bin/tools/config.py +++ b/bin/tools/config.py @@ -119,18 +119,21 @@ class configmanager(object): group = optparse.OptionGroup(parser, "Internationalisation options", "Use these options to translate Tiny ERP to another language." - "See i18n section of the user manual. Options '-l' and '-d' are mandatory.") + "See i18n section of the user manual. Option '-d' is mandatory." + "Option '-l' is mandatory in case of importation" + ) - group.add_option('-l', "--language", dest="language", help="specify the language of the translation file. Use it with --i18n-export and --i18n-import") - group.add_option("--i18n-export", dest="translate_out", help="export all sentences to be translated to a CSV or a PO file and exit") - group.add_option("--i18n-import", dest="translate_in", help="import a CSV or a PO file with translations and exit") + group.add_option('-l', "--language", dest="language", help="specify the language of the translation file. Use it with --i18n-export or --i18n-import") + group.add_option("--i18n-export", dest="translate_out", help="export all sentences to be translated to a CSV file, a PO file or a TGZ archive and exit") + group.add_option("--i18n-import", dest="translate_in", help="import a CSV or a PO file with translations and exit. The '-l' option is required.") group.add_option("--modules", dest="translate_modules", help="specify modules to export. Use in combination with --i18n-export") group.add_option("--addons-path", dest="addons_path", help="specify an alternative addons path.") parser.add_option_group(group) (opt, args) = parser.parse_args() - assert not ((opt.translate_in or opt.translate_out) and (not opt.language or not opt.db_name)), "the i18n-import and i18n-export options cannot be used without the language (-l) and database (-d) options" + assert not (opt.translate_in and (not opt.language or not opt.db_name)), "the i18n-import option cannot be used without the language (-l) and the database (-d) options" + assert not (opt.translate_out and (not opt.db_name)), "the i18n-export option cannot be used without the database (-d) option" # place/search the config file on Win32 near the server installation # (../etc from the server) diff --git a/bin/tools/translate.py b/bin/tools/translate.py index ac31af343d8..167a4ca2ab5 100644 --- a/bin/tools/translate.py +++ b/bin/tools/translate.py @@ -37,6 +37,8 @@ import netsvc from tools.misc import UpdateableStr import inspect import mx.DateTime as mxdt +import tempfile +import tarfile class UNIX_LINE_TERMINATOR(csv.excel): lineterminator = '\n' @@ -170,19 +172,45 @@ class TinyPoFile(object): # Methods to export the translation file def trans_export(lang, modules, buffer, format, dbname=None): + + def _process(format, modules, rows, buffer, lang, newlang): + if format == 'csv': + writer=csv.writer(buffer, 'UNIX') + for row in rows: + writer.writerow(row) + elif format == 'po': + rows.pop(0) + writer = tools.TinyPoFile(buffer) + writer.write_infos(modules) + for module, type, name, res_id, src, trad in rows: + writer.write(module, type, name, res_id, src, trad) + elif format == 'tgz': + rows.pop(0) + rows_by_module = {} + for row in rows: + module = row[0] + rows_by_module.setdefault(module, []).append(row) + + tmpdir = tempfile.mkdtemp() + for mod, modrows in rows_by_module.items(): + tmpmoddir = join(tmpdir, mod, 'i18n') + os.makedirs(tmpmoddir) + pofilename = (newlang and mod or lang) + ".po" + (newlang and 't' or '') + buf = open(join(tmpmoddir, pofilename), 'w') + _process('po', [mod], modrows, buf, lang, newlang) + + tar = tarfile.open(fileobj=buffer, mode='w|gz') + tar.add(tmpdir, '/') + tar.close() + + else: + raise Exception(_('Bad file format')) + + newlang = not bool(lang) + if newlang: + lang = 'en_US' trans = trans_generate(lang, modules, dbname) - if format == 'csv': - writer=csv.writer(buffer, 'UNIX') - for row in trans: - writer.writerow(row) - elif format == 'po': - trans.pop(0) - writer = tools.TinyPoFile(buffer) - writer.write_infos(modules) - for module, type, name, res_id, src, trad in trans: - writer.write(module, type, name, res_id, src, trad) - else: - raise Exception(_('Bad file format')) + _process(format, modules, trans, buffer, lang, newlang) del trans From 02a3ce47f4374908f65e0cd7a7d4433c8ae4ca39 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 15:26:12 +0200 Subject: [PATCH 070/169] Partial Commit bzr revid: fp@tinyerp.com-20080819132612-06llazok11knlwr7 --- bin/osv/orm.py | 13 +++++++++++-- bin/tools/convert.py | 13 ++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 198a3a17407..fbdb477d211 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -53,6 +53,8 @@ import string import netsvc import re +import pickle + import fields import tools @@ -432,7 +434,7 @@ class orm_template(object): return datas def import_data(self, cr, uid, fields, datas, mode='init', - current_module=None, noupdate=False, context=None): + current_module=None, noupdate=False, context=None, filename=None): if not context: context = {} fields = map(lambda x: x.split('/'), fields) @@ -591,7 +593,10 @@ class orm_template(object): fields_def = self.fields_get(cr, uid, context=context) done = 0 + initial_size = len(datas) + counter = 0 while len(datas): + counter += 1 res = {} #try: (res, other, warning, translate, data_id) = \ @@ -606,8 +611,12 @@ class orm_template(object): context2 = context.copy() context2['lang'] = lang self.write(cr, uid, [id], translate[lang], context2) - if config.get('import_partial', False): + if config.get('import_partial', False) and filename: # and (not (counter%100)) : + data = pickle.load(file(config.get('import_partial'))) + data[filename] = initial_size - len(datas) + data.get(filename, 0) + pickle.dump(data, file(config.get('import_partial'),'wb')) cr.commit() + #except Exception, e: # logger.notifyChannel("import", netsvc.LOG_ERROR, e) # cr.rollback() diff --git a/bin/tools/convert.py b/bin/tools/convert.py index 626052c060d..1cf3b9b3db9 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -724,6 +724,10 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', pool = pooler.get_pool(cr.dbname) + input = StringIO.StringIO(csvcontent) + reader = csv.reader(input, quotechar='"', delimiter=',') + fields = reader.next() + import pickle if config.get('import_partial'): if not os.path.isfile(config.get('import_partial')): @@ -732,10 +736,9 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', if fname in data: if not data[fname]: return - - input = StringIO.StringIO(csvcontent) - reader = csv.reader(input, quotechar='"', delimiter=',') - fields = reader.next() + else: + for i in range(data[fname]): + reader.next() if not (mode == 'init' or 'id' in fields): return @@ -746,7 +749,7 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', if (not line) or not reduce(lambda x,y: x or y, line) : continue datas.append( map(lambda x:x.decode('utf8').encode('utf8'), line)) - pool.get(model).import_data(cr, uid, fields, datas,mode, module,noupdate) + pool.get(model).import_data(cr, uid, fields, datas,mode, module,noupdate,filename=fname) if config.get('import_partial'): data = pickle.load(file(config.get('import_partial'))) data[fname] = 0 From dc468f95ff9befbaad79b612790f7de70941e288 Mon Sep 17 00:00:00 2001 From: Harshad Modi Date: Tue, 19 Aug 2008 18:58:54 +0530 Subject: [PATCH 071/169] crm : Sort cases by date in desc report_purchase : fixed bug if Purchase Order has not any line report_sale : Put graph views all these reports of this module. and Open in graph view by default. bzr revid: hmo@tinyerp.com-20080819132854-ae1554twlwsr5f7x --- addons/crm/crm.py | 2 +- addons/report_purchase/report_purchase.py | 2 ++ addons/report_sale/report_sale_view.xml | 35 +++++++++++++++++++---- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index 6513ca19a32..06cdf8389cc 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -379,7 +379,7 @@ class crm_case(osv.osv): 'priority': lambda *a: AVAILABLE_PRIORITIES[2][0], 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), } - _order = 'priority, date_deadline desc, id desc' + _order = 'priority, date_deadline desc, date desc,id desc' def _action(self, cr, uid, cases, state_to, scrit=None, context={}): if not scrit: diff --git a/addons/report_purchase/report_purchase.py b/addons/report_purchase/report_purchase.py index 947cbd31f34..591e9b43ebf 100644 --- a/addons/report_purchase/report_purchase.py +++ b/addons/report_purchase/report_purchase.py @@ -72,6 +72,7 @@ class report_purchase_order_product(osv.osv): from purchase_order s left join purchase_order_line l on (s.id=l.order_id) left join product_uom u on (u.id=l.product_uom) + where l.product_id is not null group by l.product_id, to_char(s.date_order, 'YYYY-MM-01'),s.state ) """) @@ -117,6 +118,7 @@ class report_purchase_order_category(osv.osv): left join product_product p on (p.id=l.product_id) left join product_template t on (t.id=p.product_tmpl_id) left join product_uom u on (u.id=l.product_uom) + where l.product_id is not null group by t.categ_id, to_char(s.date_order, 'YYYY-MM-01'),s.state ) """) diff --git a/addons/report_sale/report_sale_view.xml b/addons/report_sale/report_sale_view.xml index f68e7cd28f7..52e50f95027 100644 --- a/addons/report_sale/report_sale_view.xml +++ b/addons/report_sale/report_sale_view.xml @@ -37,12 +37,24 @@
+ + + report.sale.order.product.graph + report.sale.order.product + graph + + + + + + + Sales by Product (this month) report.sale.order.product form - tree + graph,tree [('name','=',time.strftime('%Y-%m-01'))] @@ -50,7 +62,7 @@ Sales by Product report.sale.order.product form - tree + graph,tree @@ -105,7 +117,7 @@ Sales by Category of Product (this month) report.sale.order.category form - tree + graph,tree [('name','=',time.strftime('%Y-%m-01'))] @@ -113,16 +125,27 @@ Sales by Category of Products report.sale.order.category form - tree,graph + graph,tree + + sale.order.graph + sale.order + graph + + + + + + + Sales of the Month sale.order form - tree,form + graph,tree,form [('date_order','>=',time.strftime('%Y-%m-01')),('state','<>','draft'),('state','<>','cancel')] @@ -131,4 +154,4 @@ - \ No newline at end of file + From c01a3f8ec4feb39de804bccc9affb54c97449f6c Mon Sep 17 00:00:00 2001 From: Jay Vora Date: Tue, 19 Aug 2008 19:01:12 +0530 Subject: [PATCH 072/169] Account.move.line:Added analytic entries on manual entry of move line.Added analytic journal field on account journal. bzr revid: jvo@tinyerp.com-20080819133112-qbzuy5z0kjl67jtk --- addons/account/account_move_line.py | 17 +++++- addons/account/project/project.py | 8 +++ addons/account/project/project_view.xml | 76 ++++++++++++++----------- 3 files changed, 67 insertions(+), 34 deletions(-) diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index 795bae5fb83..1476dbbe843 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -792,6 +792,7 @@ class account_move_line(osv.osv): raise osv.except_osv(_('Bad account !'), _('You can not use this general account in this journal !')) tax_go=False + if 'account_tax_id' in vals and vals['account_tax_id']: tax_id=tax_obj.browse(cr,uid,vals['account_tax_id']) @@ -804,6 +805,20 @@ class account_move_line(osv.osv): fields=[x for x in vals] tax_go=True + if 'analytic_account_id' in vals and vals['analytic_account_id']: + + if journal.analytic_journal_id: + vals['analytic_lines'] = [(0,0, { + 'name': vals['name'], + 'date': vals['date'], + 'account_id': vals['analytic_account_id'], + 'unit_amount': vals['quantity'], + 'amount': vals['debit'] or vals['credit'], + 'general_account_id': vals['account_id'], + 'journal_id': journal.analytic_journal_id.id, + 'ref': vals['ref'], + })] + result = super(osv.osv, self).create(cr, uid, vals, context) if tax_go: context['tax_line']=True @@ -823,7 +838,5 @@ class account_bank_statement_reconcile(osv.osv): } account_bank_statement_reconcile() - - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account/project/project.py b/addons/account/project/project.py index 972c4de2d64..a0eb9b8feda 100644 --- a/addons/account/project/project.py +++ b/addons/account/project/project.py @@ -237,6 +237,14 @@ class account_analytic_journal(osv.osv): } account_analytic_journal() +class account_journal(osv.osv): + _inherit="account.journal" + + _columns = { + 'analytic_journal_id':fields.many2one('account.analytic.journal','Analytic Journal'), + } +account_journal() + # --------------------------------------------------------- # Budgets diff --git a/addons/account/project/project_view.xml b/addons/account/project/project_view.xml index 8cff25efd59..27ba38f3f88 100644 --- a/addons/account/project/project_view.xml +++ b/addons/account/project/project_view.xml @@ -1,7 +1,7 @@ - + account.analytic.account.list account.analytic.account @@ -17,7 +17,7 @@
- + account.analytic.account.tree account.analytic.account @@ -37,7 +37,7 @@
- + account.analytic.account.form account.analytic.account @@ -66,7 +66,7 @@
- + Analytic Accounts ir.actions.act_window @@ -76,7 +76,7 @@ - + Analytic Charts of Accounts ir.actions.act_window @@ -86,13 +86,13 @@ - + - + account.analytic.line.form account.analytic.line @@ -141,8 +141,8 @@ - - + + action.account.tree1 account.analytic.line @@ -157,7 +157,7 @@ - + account.analytic.line.extended_form account.analytic.line @@ -183,11 +183,11 @@ form - + # # Analytic Journal # - + account.analytic.journal.tree account.analytic.journal @@ -200,7 +200,7 @@
- + account.analytic.journal.form account.analytic.journal @@ -221,11 +221,11 @@ tree,form - + # # Open journal entries # - + account.analytic.journal.open.form account.analytic.line @@ -240,19 +240,19 @@ - + # # Reporting # - + Print Analytic Journals account.analytic.journal tree - - + + Analytic Entries by Journal account.analytic.journal @@ -260,11 +260,11 @@ - + # # Statistics # - + report.hr.timesheet.invoice.journal.form report.hr.timesheet.invoice.journal @@ -277,8 +277,8 @@
- - + + report.hr.timesheet.invoice.journal.tree report.hr.timesheet.invoice.journal @@ -294,7 +294,7 @@
- + report.hr.timesheet.invoice.journal.graph report.hr.timesheet.invoice.journal @@ -308,7 +308,7 @@
- + Account cost and revenue by journal report.hr.timesheet.invoice.journal @@ -316,7 +316,7 @@ tree,graph - + Account cost and revenue by journal (This Month) report.hr.timesheet.invoice.journal @@ -325,11 +325,23 @@ [('name','=',time.strftime('%Y-%m-01'))] - + - - + + - + + + account.journal.form.1 + account.journal + + form + + + + + + + From c8fbbbf7ece629c68385b0d1ae21ae98804e0821 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 15:33:29 +0200 Subject: [PATCH 073/169] Improved Partial Commit bzr revid: fp@tinyerp.com-20080819133329-gcvrswxnby5knk3n --- bin/tools/convert.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bin/tools/convert.py b/bin/tools/convert.py index 1cf3b9b3db9..d1855e24e42 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -42,6 +42,7 @@ from config import config import logging from lxml import etree +import pickle class ConvertError(Exception): @@ -728,16 +729,16 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', reader = csv.reader(input, quotechar='"', delimiter=',') fields = reader.next() - import pickle if config.get('import_partial'): + fname_partial = module + '/'+ fname if not os.path.isfile(config.get('import_partial')): pickle.dump({}, file(config.get('import_partial'),'w+')) data = pickle.load(file(config.get('import_partial'))) - if fname in data: - if not data[fname]: + if fname_partial in data: + if not data[fname_partial]: return else: - for i in range(data[fname]): + for i in range(data[fname_partial]): reader.next() if not (mode == 'init' or 'id' in fields): @@ -749,10 +750,11 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', if (not line) or not reduce(lambda x,y: x or y, line) : continue datas.append( map(lambda x:x.decode('utf8').encode('utf8'), line)) - pool.get(model).import_data(cr, uid, fields, datas,mode, module,noupdate,filename=fname) + pool.get(model).import_data(cr, uid, fields, datas,mode, module,noupdate,filename=fname_partial) + if config.get('import_partial'): data = pickle.load(file(config.get('import_partial'))) - data[fname] = 0 + data[fname_partial] = 0 pickle.dump(data, file(config.get('import_partial'),'wb')) # From a4195c1642a43b50b6f2e139d3122b8046378c2c Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 15:34:44 +0200 Subject: [PATCH 074/169] Small Bugfix bzr revid: fp@tinyerp.com-20080819133444-egdmi1n60ly9nf2m --- bin/osv/orm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index fbdb477d211..26da17e0d83 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -611,7 +611,7 @@ class orm_template(object): context2 = context.copy() context2['lang'] = lang self.write(cr, uid, [id], translate[lang], context2) - if config.get('import_partial', False) and filename: # and (not (counter%100)) : + if config.get('import_partial', False) and filename and (not (counter%100)) : data = pickle.load(file(config.get('import_partial'))) data[filename] = initial_size - len(datas) + data.get(filename, 0) pickle.dump(data, file(config.get('import_partial'),'wb')) From 6519472370ded835301a21bc6d51f52dfc37c3fe Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 15:40:13 +0200 Subject: [PATCH 075/169] Fixed Typo bzr revid: fp@tinyerp.com-20080819134013-6002qsmwt71xu28r --- bin/tools/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/tools/config.py b/bin/tools/config.py index 2083e6fff70..fbe63161473 100644 --- a/bin/tools/config.py +++ b/bin/tools/config.py @@ -114,7 +114,7 @@ class configmanager(object): group.add_option("--db_host", dest="db_host", help="specify the database host") group.add_option("--db_port", dest="db_port", help="specify the database port") group.add_option("--db_maxconn", dest="db_maxconn", default='64', help="specify the the maximum number of physical connections to posgresql") - group.add_option("-C", "--commit-mode", dest="import_partial", help="Several commit during one file importation. Use this for big data importation. Provide a filename to store intermediate state.", default=False) + group.add_option("-P", "--import-partial", dest="import_partial", help="Use this for big data importation, if it crashes you will be able to continue at the current state. Provide a filename to store intermediate importation states.", default=False) parser.add_option_group(group) group = optparse.OptionGroup(parser, "Internationalisation options", From 2015a95d5a9c5e5b43ebf13c88b8905d33a52225 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 20:07:47 +0200 Subject: [PATCH 076/169] Schedulers Bugfix bzr revid: fp@tinyerp.com-20080819180747-h48nvqfq4118o7dt --- addons/mrp/wizard/schedulers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/mrp/wizard/schedulers.py b/addons/mrp/wizard/schedulers.py index 232e5b13d96..206e30bbab3 100644 --- a/addons/mrp/wizard/schedulers.py +++ b/addons/mrp/wizard/schedulers.py @@ -47,8 +47,9 @@ def _procure_confirm(self, cr, uid, schedule_cycle=1.0, po_cycle=1.0,\ cr = pooler.get_db(use_new_cursor).cursor() wf_service = netsvc.LocalService("workflow") + orderpoint_obj = pooler.get_pool(cr.dbname).get('stock.warehouse.orderpoint') procurement_obj = pooler.get_pool(cr.dbname).get('mrp.procurement') - ids = procurement_obj.search(cr, uid, [('state', '=', 'exception')], order='date_planned') + ids=orderpoint_obj.search(cr,uid,[], order="date_planned") for id in ids: wf_service.trg_validate(uid, 'mrp.procurement', id, 'button_restart', cr) if use_new_cursor: From fc62fe34d2b96cc4074b51cd711cb062b40f5384 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 21:25:52 +0200 Subject: [PATCH 077/169] Properties: removed old tag bzr revid: fp@tinyerp.com-20080819192552-ji5cxr3ykt0r1qs2 --- bin/addons/base/base.sql | 1 - bin/addons/base/ir/ir_model.py | 1 - bin/addons/base/ir/ir_ui_view.py | 1 + bin/addons/base/res/partner/partner_view.xml | 1 - 4 files changed, 1 insertion(+), 3 deletions(-) diff --git a/bin/addons/base/base.sql b/bin/addons/base/base.sql index 5b0ffb138e4..044a2d8ebb9 100644 --- a/bin/addons/base/base.sql +++ b/bin/addons/base/base.sql @@ -40,7 +40,6 @@ CREATE TABLE ir_model_fields ( select_level varchar(4), field_description varchar(256), ttype varchar(64), - group_name varchar(64), state varchar(64) default 'base', view_load boolean, relate boolean default False, diff --git a/bin/addons/base/ir/ir_model.py b/bin/addons/base/ir/ir_model.py index e811c25ef7d..094e996a03d 100644 --- a/bin/addons/base/ir/ir_model.py +++ b/bin/addons/base/ir/ir_model.py @@ -120,7 +120,6 @@ class ir_model_fields(osv.osv): 'domain': fields.char('Domain', size=256), 'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'), - 'group_name': fields.char('Group Name', size=128), 'view_load': fields.boolean('View Auto-Load'), 'complete_name': fields.char('Complete Name', required=True, size=64, select=1), } diff --git a/bin/addons/base/ir/ir_ui_view.py b/bin/addons/base/ir/ir_ui_view.py index 84442427145..6bf326bd33e 100644 --- a/bin/addons/base/ir/ir_ui_view.py +++ b/bin/addons/base/ir/ir_ui_view.py @@ -35,6 +35,7 @@ import netsvc import os def _check_xml(self, cr, uid, ids, context={}): + return True for view in self.browse(cr, uid, ids, context): eview = etree.fromstring(view.arch) frng = tools.file_open(os.path.join('base','rng','view.rng')) diff --git a/bin/addons/base/res/partner/partner_view.xml b/bin/addons/base/res/partner/partner_view.xml index 8f16f953615..84cdb89b97c 100644 --- a/bin/addons/base/res/partner/partner_view.xml +++ b/bin/addons/base/res/partner/partner_view.xml @@ -280,7 +280,6 @@
- From 7b9668516703d4d9be090f276be520f665f3d00a Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 21:26:29 +0200 Subject: [PATCH 078/169] Modifications on properties in views bzr revid: fp@tinyerp.com-20080819192629-1nu8lwr8x1xq8o2t --- addons/account/invoice.py | 3 +- addons/account/partner.py | 14 +++++--- addons/account/partner_view.xml | 27 ++++++++++++++-- addons/account/product.py | 4 --- addons/account/product_view.xml | 54 +++++++++++++++++++++++++++++-- addons/delivery/__terp__.py | 2 +- addons/delivery/partner.py | 1 - addons/mrp/wizard/schedulers.py | 3 +- addons/product/__terp__.py | 3 +- addons/product/partner.py | 1 - addons/product/product_view.xml | 5 +-- addons/purchase/__terp__.py | 3 +- addons/purchase/partner.py | 1 - addons/purchase/purchase.py | 16 +++++++++ addons/report_sale/report_sale.py | 9 ------ addons/stock/__terp__.py | 11 ++++++- addons/stock/partner.py | 2 -- addons/stock/product.py | 8 ----- 18 files changed, 120 insertions(+), 47 deletions(-) diff --git a/addons/account/invoice.py b/addons/account/invoice.py index b124bf5a602..9a49a871f42 100644 --- a/addons/account/invoice.py +++ b/addons/account/invoice.py @@ -790,9 +790,9 @@ class account_invoice_line(osv.osv): taxep=None if partner_id: lang=self.pool.get('res.partner').read(cr, uid, [partner_id])[0]['lang'] - taxep = self.pool.get('res.partner').browse(cr, uid, partner_id).property_account_tax tax_obj = self.pool.get('account.tax') if type in ('out_invoice', 'out_refund'): + taxep = self.pool.get('res.partner').browse(cr, uid, partner_id).property_account_supplier_tax if not taxep or not taxep.id: tax_id = map(lambda x: x.id, res.taxes_id) else: @@ -801,6 +801,7 @@ class account_invoice_line(osv.osv): if not t.tax_group==taxep.tax_group: tax_id.append(t.id) else: + taxep = self.pool.get('res.partner').browse(cr, uid, partner_id).property_account_tax if not taxep or not taxep.id: tax_id = map(lambda x: x.id, res.supplier_taxes_id) else: diff --git a/addons/account/partner.py b/addons/account/partner.py index b6bcb2324b2..db1179c7a54 100644 --- a/addons/account/partner.py +++ b/addons/account/partner.py @@ -97,7 +97,6 @@ class res_partner(osv.osv): string="Account Payable", method=True, view_load=True, - group_name="Accounting Properties", domain="[('type', '=', 'payable')]", help="This account will be used, instead of the default one, as the payable account for the current partner", required=True), @@ -108,10 +107,17 @@ class res_partner(osv.osv): string="Account Receivable", method=True, view_load=True, - group_name="Accounting Properties", domain="[('type', '=', 'receivable')]", help="This account will be used, instead of the default one, as the receivable account for the current partner", required=True), + 'property_account_supplier_tax': fields.property( + 'account.tax', + type='many2one', + relation='account.tax', + string="Default Supplier Tax", + method=True, + view_load=True, + help="This tax will be used, instead of the default one for supplier invoices."), 'property_account_tax': fields.property( 'account.tax', type='many2one', @@ -119,8 +125,7 @@ class res_partner(osv.osv): string="Default Tax", method=True, view_load=True, - group_name="Accounting Properties", - help="This tax will be used, instead of the default one."), + help="This tax will be used, instead of the default one for customers."), 'property_payment_term': fields.property( 'account.payment.term', type='many2one', @@ -128,7 +133,6 @@ class res_partner(osv.osv): string ='Payment Term', method=True, view_load=True, - group_name="Accounting Properties", help="This payment term will be used, instead of the default one, for the current partner"), 'ref_companies': fields.one2many('res.company', 'partner_id', 'Companies that refers to partner'), diff --git a/addons/account/partner_view.xml b/addons/account/partner_view.xml index 88ea1d898f8..ccc93735a59 100644 --- a/addons/account/partner_view.xml +++ b/addons/account/partner_view.xml @@ -2,8 +2,8 @@ + Partners Extension + --> res.partner.form.inherit @@ -18,5 +18,26 @@
+ + + res.partner.property.form.inherit + res.partner + form + 2 + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/addons/account/product.py b/addons/account/product.py index c7eca8e1253..ef1d11312bc 100644 --- a/addons/account/product.py +++ b/addons/account/product.py @@ -42,7 +42,6 @@ class product_category(osv.osv): string="Income Account", method=True, view_load=True, - group_name="Accounting Properties", help="This account will be used, instead of the default one, to value incoming stock for the current product category"), 'property_account_expense_categ': fields.property( 'account.account', @@ -51,7 +50,6 @@ class product_category(osv.osv): string="Expense Account", method=True, view_load=True, - group_name="Accounting Properties", help="This account will be used, instead of the default one, to value outgoing stock for the current product category"), } product_category() @@ -75,7 +73,6 @@ class product_template(osv.osv): string="Income Account", method=True, view_load=True, - group_name="Accounting Properties", help="This account will be used, instead of the default one, to value incoming stock for the current product"), 'property_account_expense': fields.property( 'account.account', @@ -84,7 +81,6 @@ class product_template(osv.osv): string="Expense Account", method=True, view_load=True, - group_name="Accounting Properties", help="This account will be used, instead of the default one, to value outgoing stock for the current product"), } product_template() diff --git a/addons/account/product_view.xml b/addons/account/product_view.xml index d4ffd9ea9a5..210788ae7ee 100644 --- a/addons/account/product_view.xml +++ b/addons/account/product_view.xml @@ -15,7 +15,7 @@
- + product.template.product.form.inherit product.template @@ -30,5 +30,55 @@
+ + + + + product.category.property.form.inherit + product.category + form + + + + + + + + + + + + + product.template.property.form.inherit + product.template + form + + + + + + + + + + + + + + product.normal.property.form.inherit + product.product + form + + + + + + + + + + + + - \ No newline at end of file + diff --git a/addons/delivery/__terp__.py b/addons/delivery/__terp__.py index 20e7b89f915..85a525b5e31 100644 --- a/addons/delivery/__terp__.py +++ b/addons/delivery/__terp__.py @@ -35,7 +35,7 @@ "depends" : ["sale","purchase", "stock",], "init_xml" : ["delivery_data.xml"], "demo_xml" : ["delivery_demo.xml"], - "update_xml" : ["delivery_view.xml","delivery_wizard.xml"], + "update_xml" : ["delivery_view.xml","delivery_wizard.xml","partner_view.xml"], "active": False, "installable": True, } diff --git a/addons/delivery/partner.py b/addons/delivery/partner.py index a3fb2b81a55..ae68ceb77a4 100644 --- a/addons/delivery/partner.py +++ b/addons/delivery/partner.py @@ -40,7 +40,6 @@ class res_partner(osv.osv): string="Delivery Method", method=True, view_load=True, - group_name="Delivery Properties", help="This carrier will be used, instead of the default one, for delivering goods to the current partner"), } res_partner() diff --git a/addons/mrp/wizard/schedulers.py b/addons/mrp/wizard/schedulers.py index 206e30bbab3..98ff98111ea 100644 --- a/addons/mrp/wizard/schedulers.py +++ b/addons/mrp/wizard/schedulers.py @@ -47,9 +47,8 @@ def _procure_confirm(self, cr, uid, schedule_cycle=1.0, po_cycle=1.0,\ cr = pooler.get_db(use_new_cursor).cursor() wf_service = netsvc.LocalService("workflow") - orderpoint_obj = pooler.get_pool(cr.dbname).get('stock.warehouse.orderpoint') procurement_obj = pooler.get_pool(cr.dbname).get('mrp.procurement') - ids=orderpoint_obj.search(cr,uid,[], order="date_planned") + ids=procurement_obj.search(cr,uid,[], order="date_planned") for id in ids: wf_service.trg_validate(uid, 'mrp.procurement', id, 'button_restart', cr) if use_new_cursor: diff --git a/addons/product/__terp__.py b/addons/product/__terp__.py index 7a99d4327d1..83273f65a6c 100644 --- a/addons/product/__terp__.py +++ b/addons/product/__terp__.py @@ -53,7 +53,8 @@ Print product labels with barcodes. """, "update_xml" : ["product_data.xml","product_report.xml", - "product_view.xml", "pricelist_view.xml","product_security.xml"], + "product_view.xml", "pricelist_view.xml","product_security.xml", + "partner_view.xml"], "active": False, "installable": True } diff --git a/addons/product/partner.py b/addons/product/partner.py index 1435b174742..51a9408878f 100644 --- a/addons/product/partner.py +++ b/addons/product/partner.py @@ -43,7 +43,6 @@ class res_partner(osv.osv): string="Sale Pricelist", method=True, view_load=True, - group_name="Pricelists Properties", help="This pricelist will be used, instead of the default one, \ for sales to the current partner"), } diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index 5b3bdbd279a..7c13df2ac63 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -102,7 +102,6 @@
- @@ -127,8 +126,7 @@ - - + @@ -441,7 +439,6 @@ - diff --git a/addons/purchase/__terp__.py b/addons/purchase/__terp__.py index e7eae7b083d..53fb104445e 100644 --- a/addons/purchase/__terp__.py +++ b/addons/purchase/__terp__.py @@ -45,7 +45,8 @@ "purchase_report.xml", "purchase_wizard.xml", "stock_view.xml", - "purchase_security.xml" + "purchase_security.xml", + "partner_view.xml" ], "active": False, "installable": True diff --git a/addons/purchase/partner.py b/addons/purchase/partner.py index 9ae85eacd22..ab831b66bd1 100644 --- a/addons/purchase/partner.py +++ b/addons/purchase/partner.py @@ -41,7 +41,6 @@ class res_partner(osv.osv): string="Purchase Pricelist", method=True, view_load=True, - group_name="Pricelists Properties", help="This pricelist will be used, instead of the default one, for purchases from the current partner"), } res_partner() diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 36db7bb9a02..35c46825008 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -366,6 +366,22 @@ class purchase_order_line(osv.osv): res = {'value': {'price_unit': price, 'name':prod_name, 'taxes_id':prod['supplier_taxes_id'], 'date_planned': dt,'notes':prod['description_purchase'], 'product_uom': uom}} domain = {} + if res['value']['taxes_id']: + taxes = self.pool.get('account.tax').browse(cr, uid, + [x.id for x in product.supplier_taxes_id]) + taxep = None + if partner_id: + taxep = self.pool.get('res.partner').browse(cr, uid, + partner_id).property_account_supplier_tax + if not taxep or not taxep.id: + res['value']['taxes_id'] = [x.id for x in product.taxes_id] + else: + res5 = [taxep.id] + for t in taxes: + if not t.tax_group==taxep.tax_group: + res5.append(t.id) + res['value']['taxes_id'] = res5 + res2 = self.pool.get('product.uom').read(cr, uid, [uom], ['category_id']) res3 = self.pool.get('product.uom').read(cr, uid, [prod_uom_po], ['category_id']) domain = {'product_uom':[('category_id','=',res2[0]['category_id'][0])]} diff --git a/addons/report_sale/report_sale.py b/addons/report_sale/report_sale.py index 8328b7f037c..9407acddcd8 100644 --- a/addons/report_sale/report_sale.py +++ b/addons/report_sale/report_sale.py @@ -71,15 +71,6 @@ class report_sale_order_product(osv.osv): group by l.product_id, to_char(s.date_order, 'YYYY-MM-01'),s.state ) """) -# Done in the _auto_init -# for k in self._columns: -# f = self._columns[k] -# cr.execute("select id from ir_model_fields where model=%s and name=%s", (self._name,k)) -# if not cr.rowcount: -# cr.execute("select id from ir_model where model='%s'" % self._name) -# model_id = cr.fetchone()[0] -# cr.execute("INSERT INTO ir_model_fields (model_id, model, name, field_description, ttype, relate,relation,group_name,view_load) VALUES (%d,%s,%s,%s,%s,%s,%s,%s,%s)", (model_id, self._name, k, f.string.replace("'", " "), f._type, (f.relate and 'True') or 'False', f._obj or 'NULL', f.group_name or '', (f.view_load and 'True') or 'False')) - report_sale_order_product() class report_sale_order_category(osv.osv): diff --git a/addons/stock/__terp__.py b/addons/stock/__terp__.py index 248b0414370..0a498ac0ef9 100644 --- a/addons/stock/__terp__.py +++ b/addons/stock/__terp__.py @@ -35,7 +35,16 @@ "category" : "Generic Modules/Inventory Control", "init_xml" : [], "demo_xml" : ["stock_demo.xml"], - "update_xml" : ["stock_workflow.xml", "stock_data.xml", "stock_incoterms.xml","stock_wizard.xml", "stock_view.xml", "stock_report.xml", "stock_sequence.xml", "product_data.xml",], + "update_xml" : [ + "stock_workflow.xml", + "stock_data.xml", + "stock_incoterms.xml","stock_wizard.xml", + "stock_view.xml", + "stock_report.xml", + "stock_sequence.xml", + "product_data.xml", + "product_view.xml" + ], "active": False, "installable": True } diff --git a/addons/stock/partner.py b/addons/stock/partner.py index d3770697f62..41c06fcb557 100644 --- a/addons/stock/partner.py +++ b/addons/stock/partner.py @@ -42,7 +42,6 @@ class res_partner(osv.osv): string="Customer Location", method=True, view_load=True, - group_name="Inventory Properties", help="This stock location will be used, instead of the default one, as the destination location for goods you send to this partner"), 'property_stock_supplier': fields.property( 'stock.location', @@ -51,7 +50,6 @@ class res_partner(osv.osv): string="Supplier Location", method=True, view_load=True, - group_name="Inventory Properties", help="This stock location will be used, instead of the default one, as the source location for goods you receive from the current partner"), } res_partner() diff --git a/addons/stock/product.py b/addons/stock/product.py index f90a1be63a9..5834a187c22 100644 --- a/addons/stock/product.py +++ b/addons/stock/product.py @@ -42,7 +42,6 @@ class product_product(osv.osv): string="Procurement Location", method=True, view_load=True, - group_name="Inventory Properties", help="For the current product (template), this stock location will be used, instead of the default one, as the source location for stock moves generated by procurements"), 'property_stock_production': fields.property( 'stock.location', @@ -51,7 +50,6 @@ class product_product(osv.osv): string="Production Location", method=True, view_load=True, - group_name="Inventory Properties", help="For the current product (template), this stock location will be used, instead of the default one, as the source location for stock moves generated by production orders"), 'property_stock_inventory': fields.property( 'stock.location', @@ -60,17 +58,14 @@ class product_product(osv.osv): string="Inventory Location", method=True, view_load=True, - group_name="Inventory Properties", help="For the current product (template), this stock location will be used, instead of the default one, as the source location for stock moves generated when you do an inventory"), 'property_stock_account_input': fields.property('account.account', type='many2one', relation='account.account', string='Stock Input Account', method=True, view_load=True, - group_name='Stock Accounting Properties', help='This account will be used, instead of the default one, to value input stock'), 'property_stock_account_output': fields.property('account.account', type='many2one', relation='account.account', string='Stock Output Account', method=True, view_load=True, - group_name='Stock Accounting Properties', help='This account will be used, instead of the default one, to value output stock'), } @@ -83,17 +78,14 @@ class product_category(osv.osv): 'property_stock_journal': fields.property('account.journal', relation='account.journal', type='many2one', string='Stock journal', method=True, view_load=True, - group_name="Accounting Properties", help="This journal will be used for the accounting move generated by stock move"), 'property_stock_account_input_categ': fields.property('account.account', type='many2one', relation='account.account', string='Stock Input Account', method=True, view_load=True, - group_name='Stock Accounting Properties', help='This account will be used to value the input stock'), 'property_stock_account_output_categ': fields.property('account.account', type='many2one', relation='account.account', string='Stock Output Account', method=True, view_load=True, - group_name='Stock Accounting Properties', help='This account will be used to value the output stock'), } From 62ae90231becb2539c1f33c699fcda4f934dfa1a Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 23:42:53 +0200 Subject: [PATCH 079/169] Bugfix ORM _inherits bzr revid: fp@tinyerp.com-20080819214253-nd4e5ym37lhyv2jj --- bin/osv/expression.py | 9 +++++---- bin/osv/orm.py | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bin/osv/expression.py b/bin/osv/expression.py index bf6b8f9fe50..abdb9990137 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -84,7 +84,8 @@ class expression(object): if left in table._inherit_fields: working_table = table.pool.get(table._inherit_fields[left][0]) if working_table not in self.__tables.values(): - self.__joins.append('%s.%s' % (table._table, table._inherits[working_table._name])) + self.__joins.append(('%s.%s=%s.%s' % (working_table._table, 'id', table._table, table._inherits[working_table._name]), working_table._table)) + print 'JOINS', self.__joins self.__tables[i] = working_table @@ -286,13 +287,13 @@ class expression(object): stack.append('(%s %s %s)' % (q1, ops[e], q2,)) query = ' AND '.join(reversed(stack)) - joins = ' AND '.join(map(lambda j: '%s.id = %s' % (self.__main_table._table, j), self.__joins)) + joins = ' AND '.join(map(lambda j: j[0], self.__joins)) if joins: - query = '(%s AND (%s))' % (joins, query) + query = '(%s) AND (%s)' % (joins, query) return (query, flatten(params)) def get_tables(self): - return ['"%s"' % t._table for t in set(self.__tables.values())] + return ['"%s"' % t._table for t in set(self.__tables.values()+[self.__main_table])] # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 26da17e0d83..8c1dd59bf25 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -2336,6 +2336,7 @@ class orm(orm_template): else: qu1, qu2, tables = [], [], ['"%s"' % self._table] + print 'WC RES', qu1, qu2, tables return (qu1, qu2, tables) def _check_qorder(self, word): @@ -2345,10 +2346,12 @@ class orm(orm_template): def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False): + print 'SEARCH', self._name, args if not context: context = {} # compute the where, order by, limit and offset clauses (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context) + print qu1, qu2, tables if len(qu1): qu1 = ' where '+string.join(qu1, ' and ') From df8de890421501cdafb578a06f44cdce625201a1 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 23:48:49 +0200 Subject: [PATCH 080/169] Removed print statement bzr revid: fp@tinyerp.com-20080819214849-ltxf8l0n12ku6its --- bin/osv/orm.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 8c1dd59bf25..26da17e0d83 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -2336,7 +2336,6 @@ class orm(orm_template): else: qu1, qu2, tables = [], [], ['"%s"' % self._table] - print 'WC RES', qu1, qu2, tables return (qu1, qu2, tables) def _check_qorder(self, word): @@ -2346,12 +2345,10 @@ class orm(orm_template): def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False): - print 'SEARCH', self._name, args if not context: context = {} # compute the where, order by, limit and offset clauses (qu1, qu2, tables) = self._where_calc(cr, user, args, context=context) - print qu1, qu2, tables if len(qu1): qu1 = ' where '+string.join(qu1, ' and ') From ba082a347da5cf1b0b55da7e50411677d49001e4 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 19 Aug 2008 23:49:26 +0200 Subject: [PATCH 081/169] Bugfixes bzr revid: fp@tinyerp.com-20080819214926-jhjchub2iow3h30b --- addons/account/account.py | 27 ++++++++++++----------- addons/hr_timesheet/hr_timesheet_view.xml | 12 +++++++++- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/addons/account/account.py b/addons/account/account.py index b56b86e21fc..8e0b0d1abd2 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -177,20 +177,21 @@ class account_account(osv.osv): } ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)]) acc_set = ",".join(map(str, ids2)) - query = self.pool.get('account.move.line')._query_get(cr, uid, - context=context) - cr.execute(("SELECT l.account_id, " +\ - ' , '.join(map(lambda x: mapping[x], field_names)) + - "FROM " \ - "account_move_line l " \ - "WHERE " \ - "l.account_id IN (%s) " \ - "AND " + query + " " \ - "GROUP BY l.account_id") % (acc_set, )) - accounts = {} - for res in cr.fetchall(): - accounts[res[0]] = res[1:] + if ids2: + query = self.pool.get('account.move.line')._query_get(cr, uid, + context=context) + cr.execute(("SELECT l.account_id, " +\ + ' , '.join(map(lambda x: mapping[x], field_names)) + + "FROM " \ + "account_move_line l " \ + "WHERE " \ + "l.account_id IN (%s) " \ + "AND " + query + " " \ + "GROUP BY l.account_id") % (acc_set, )) + + for res in cr.fetchall(): + accounts[res[0]] = res[1:] res = {} for id in ids: diff --git a/addons/hr_timesheet/hr_timesheet_view.xml b/addons/hr_timesheet/hr_timesheet_view.xml index e1995546d75..da7a0089b20 100644 --- a/addons/hr_timesheet/hr_timesheet_view.xml +++ b/addons/hr_timesheet/hr_timesheet_view.xml @@ -60,9 +60,19 @@ + + My Working Hours of The Day + ir.actions.act_window + hr.analytic.timesheet + form + tree,form + [('user_id','=',uid),('date', '=',time.strftime('%Y-%m-%d'))] + + + - Workin Hours + Working Hours ir.actions.act_window hr.analytic.timesheet form From 05d39227513ad00acafa9210e7c0942c16dd79f3 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 20 Aug 2008 00:38:20 +0200 Subject: [PATCH 082/169] Better Warning Message when employee misconfigured for timesheet lp bug: https://launchpad.net/bugs/259465 fixed bzr revid: fp@tinyerp.com-20080819223820-znehuxw9ikxo9qqs --- addons/account/account_analytic_line.py | 6 +++--- addons/hr_timesheet/hr_timesheet.py | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/addons/account/account_analytic_line.py b/addons/account/account_analytic_line.py index 8e8a20e7260..68559fd48b1 100644 --- a/addons/account/account_analytic_line.py +++ b/addons/account/account_analytic_line.py @@ -46,9 +46,9 @@ class account_analytic_line(osv.osv): 'product_uom_id' : fields.many2one('product.uom', 'UoM'), 'product_id' : fields.many2one('product.product', 'Product'), 'account_id' : fields.many2one('account.analytic.account', 'Analytic Account', required=True, ondelete='cascade', select=True), - 'general_account_id' : fields.many2one('account.account', 'General account', required=True, ondelete='cascade'), - 'move_id' : fields.many2one('account.move.line', 'General entry', ondelete='cascade', select=True), - 'journal_id' : fields.many2one('account.analytic.journal', 'Analytic journal', required=True, ondelete='cascade', select=True), + 'general_account_id' : fields.many2one('account.account', 'General Account', required=True, ondelete='cascade'), + 'move_id' : fields.many2one('account.move.line', 'Move Line', ondelete='cascade', select=True), + 'journal_id' : fields.many2one('account.analytic.journal', 'Analytic Journal', required=True, ondelete='cascade', select=True), 'code' : fields.char('Code', size=8), 'user_id' : fields.many2one('res.users', 'User',), 'ref': fields.char('Ref.', size=32), diff --git a/addons/hr_timesheet/hr_timesheet.py b/addons/hr_timesheet/hr_timesheet.py index 7d0d4e8f172..6c26b7f1c6b 100644 --- a/addons/hr_timesheet/hr_timesheet.py +++ b/addons/hr_timesheet/hr_timesheet.py @@ -31,6 +31,7 @@ import time from osv import fields from osv import osv +from osv.orm import except_orm class hr_employee(osv.osv): @@ -116,7 +117,16 @@ class hr_analytic_timesheet(osv.osv): 'date' : lambda self,cr,uid,ctx: ctx.get('date', time.strftime('%Y-%m-%d')), 'user_id' : lambda obj, cr, uid, ctx : ctx.get('user_id', uid), } - + def create(self, cr, uid, vals, context={}): + try: + res = super(hr_analytic_timesheet, self).create(cr, uid, vals, context) + return res + except Exception,e: + if 'journal_id' in e.args[0]: + raise except_orm(_('ValidateError'), + _('No analytic journal available for this employee.\nDefine an employee for the selected user and assign an analytic journal.')) + else: + raise def on_change_user_id(self, cr, uid, ids, user_id): if not user_id: @@ -126,10 +136,7 @@ class hr_analytic_timesheet(osv.osv): 'product_uom_id' : self._getEmployeeUnit(cr, user_id, context= {}), 'general_account_id' :self. _getGeneralAccount(cr, user_id, context= {}), 'journal_id' : self._getAnalyticJournal(cr, user_id, context= {}), - }} - - - + }} hr_analytic_timesheet() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From a7b0cfd6f5da926aa8c423b81fb4c3f47deadb5e Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 20 Aug 2008 00:52:54 +0200 Subject: [PATCH 083/169] Fix From C2C: ~c2c/openobject-server/openobject-server-log-bug Bugfixes in Expression bzr revid: fp@tinyerp.com-20080819225254-6gg6j2dalz666oti --- bin/netsvc.py | 11 ++--------- bin/osv/expression.py | 6 ++++-- bin/osv/osv.py | 11 ----------- bin/sql_db.py | 1 + 4 files changed, 7 insertions(+), 22 deletions(-) diff --git a/bin/netsvc.py b/bin/netsvc.py index c8e1f0d3b36..aa5724e4f97 100644 --- a/bin/netsvc.py +++ b/bin/netsvc.py @@ -160,15 +160,8 @@ def init_logger(): logf = config['logfile'] # test if the directories exist, else create them try: - if not os.path.exists(os.path.dirname(logf)): - os.makedirs(os.path.dirname(logf)) - try: - fd = open(logf, 'a') - handler = logging.StreamHandler(fd) - except IOError: - sys.stderr.write("ERROR: couldn't open the logfile\n") - handler = logging.StreamHandler(sys.stdout) - except OSError: + handler = logging.handlers.TimedRotatingFileHandler(logf,'D',1,30) + except: sys.stderr.write("ERROR: couldn't create the logfile directory\n") handler = logging.StreamHandler(sys.stdout) else: diff --git a/bin/osv/expression.py b/bin/osv/expression.py index abdb9990137..237e4ca36d7 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -68,7 +68,7 @@ class expression(object): if not ids: return [] ids2 = table.search(cr, uid, [(parent, 'in', ids)], context=context) - return [(prefix+left, 'in', ids2+ids)] + return [(prefix+left, 'in', ids + _rec_get(ids2, table, parent))] self.__main_table = table @@ -164,9 +164,11 @@ class expression(object): self.__operator = 'in' if field._obj != working_table._name: - dom = _rec_get(ids2, field_obj, working_table._parent_name,) + dom = _rec_get(ids2, field_obj, working_table._parent_name, left=left) + print 'Diff', dom, field._obj else: dom = _rec_get(ids2, working_table, left) + print 'Same', dom self.__exp = self.__exp[:i] + dom + self.__exp[i+1:] else: if isinstance(right, basestring): diff --git a/bin/osv/osv.py b/bin/osv/osv.py index 611ba504955..6f883444fef 100644 --- a/bin/osv/osv.py +++ b/bin/osv/osv.py @@ -165,19 +165,11 @@ class osv_pool(netsvc.Service): #TODO: pass a list of modules to load def instanciate(self, module, cr): -# print "module list:", module_list -# for module in module_list: res = [] class_list = module_class_list.get(module, []) -# if module not in self.module_object_list: -# print "%s class_list:" % module, class_list for klass in class_list: res.append(klass.createInstance(self, module, cr)) return res -# else: -# print "skipping module", module - -#pooler.get_pool(cr.dbname) = osv_pool() class osv_memory(orm.orm_memory): #__metaclass__ = inheritor @@ -236,9 +228,6 @@ class osv(orm.orm): parent_name = hasattr(cls, '_inherit') and cls._inherit if parent_name: parent_class = pool.get(parent_name).__class__ - - print cls - print cls._name assert pool.get(parent_name), "parent class %s does not exist in module %s !" % (parent_name, module) nattr = {} for s in ('_columns', '_defaults', '_inherits', '_constraints', '_sql_constraints'): diff --git a/bin/sql_db.py b/bin/sql_db.py index 05a5577490c..9fc30ba1242 100644 --- a/bin/sql_db.py +++ b/bin/sql_db.py @@ -59,6 +59,7 @@ class fake_cursor: self.dbname = dbname def execute(self, sql, params=None): + print sql,params if not params: params=() def base_string(s): From 840e3c1082a0dc01926d842c7758d723358af24d Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 20 Aug 2008 00:53:29 +0200 Subject: [PATCH 084/169] Remove print statement bzr revid: fp@tinyerp.com-20080819225329-15vnv8wvxa4qekml --- bin/sql_db.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/sql_db.py b/bin/sql_db.py index 9fc30ba1242..05a5577490c 100644 --- a/bin/sql_db.py +++ b/bin/sql_db.py @@ -59,7 +59,6 @@ class fake_cursor: self.dbname = dbname def execute(self, sql, params=None): - print sql,params if not params: params=() def base_string(s): From 6fa9175cc58f83eb39e8d988734b1d27c57f4a5c Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 20 Aug 2008 01:15:37 +0200 Subject: [PATCH 085/169] Bugfixes in Expression bzr revid: fp@tinyerp.com-20080819231537-akvupz2a38i6jfic --- bin/osv/expression.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/bin/osv/expression.py b/bin/osv/expression.py index 237e4ca36d7..9855a4a5d91 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -65,10 +65,12 @@ class expression(object): return [(left, 'in', table.search(cr, uid, doms, context=context))] return doms else: - if not ids: - return [] - ids2 = table.search(cr, uid, [(parent, 'in', ids)], context=context) - return [(prefix+left, 'in', ids + _rec_get(ids2, table, parent))] + def rg(ids, table, parent): + if not ids: + return [] + ids2 = table.search(cr, uid, [(parent, 'in', ids)], context=context) + return ids+rg(ids2, table, parent) + return [(prefix+left, 'in', rg(ids2, table, parent))] self.__main_table = table @@ -85,7 +87,6 @@ class expression(object): working_table = table.pool.get(table._inherit_fields[left][0]) if working_table not in self.__tables.values(): self.__joins.append(('%s.%s=%s.%s' % (working_table._table, 'id', table._table, table._inherits[working_table._name]), working_table._table)) - print 'JOINS', self.__joins self.__tables[i] = working_table @@ -165,10 +166,8 @@ class expression(object): self.__operator = 'in' if field._obj != working_table._name: dom = _rec_get(ids2, field_obj, working_table._parent_name, left=left) - print 'Diff', dom, field._obj else: dom = _rec_get(ids2, working_table, left) - print 'Same', dom self.__exp = self.__exp[:i] + dom + self.__exp[i+1:] else: if isinstance(right, basestring): From 2656522a633869f2820021af1c4ecdcc915f9a96 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Wed, 20 Aug 2008 10:20:19 +0200 Subject: [PATCH 086/169] add rpc method to get the version of the server bzr revid: christophe@tinyerp.com-20080820082019-3v0d2gzjzchsb942 --- bin/service/web_services.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bin/service/web_services.py b/bin/service/web_services.py index 405fea724ac..20473e6388a 100644 --- a/bin/service/web_services.py +++ b/bin/service/web_services.py @@ -56,6 +56,7 @@ class db(netsvc.Service): self.exportMethod(self.list) self.exportMethod(self.list_lang) self.exportMethod(self.change_admin_password) + self.exportMethod(self.server_version) self.actions = {} self.id = 0 self.id_protect = threading.Semaphore() @@ -281,6 +282,12 @@ class db(netsvc.Service): lang = os.path.basename(fname).split(".")[0] return (lang, lang_dict.get(lang, lang)) return [lang_tuple(fname) for fname in file_list] + + def server_version(self): + """ Return the version of the server + Used by the client to verify the compatibility with its own version + """ + return tinyerp_version db() class common(netsvc.Service): From a6728b561662f73393c4675b2aeaa18095c519ae Mon Sep 17 00:00:00 2001 From: "mga@tinyerp.com" <> Date: Wed, 20 Aug 2008 15:51:43 +0530 Subject: [PATCH 087/169] * change the order first load models then load actions * overrides the name_search method of ir.model.fields for server action bzr revid: mga@tinyerp.com-20080820102143-wc1gptqmcu0l679m --- bin/addons/base/ir/__init__.py | 2 +- bin/addons/base/ir/ir_actions.py | 104 +++++++++++++++---------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/bin/addons/base/ir/__init__.py b/bin/addons/base/ir/__init__.py index 2d8e697adeb..f6d3f769213 100644 --- a/bin/addons/base/ir/__init__.py +++ b/bin/addons/base/ir/__init__.py @@ -31,9 +31,9 @@ import ir_sequence import ir_ui_menu import ir_ui_view -import ir_actions import ir_default import ir_model +import ir_actions import ir_report_custom import ir_attachment import ir_cron diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index 9ed4565169a..4e65045ab29 100755 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -270,58 +270,58 @@ def model_get(self, cr, uid, context={}): return res -#class ir_model_fields(osv.osv): -# _inherit = 'ir.model.fields' -# _rec_name = 'complete_name' -# _columns = { -# 'complete_name': fields.char('Complete Name', required=True, size=64, select=1), -# } -# -# def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=80): -# def get_fields(cr, uid, field, rel): -# result = [] -# mobj = self.pool.get('ir.model') -# id = mobj.search(cr, uid, [('model','=',rel)]) -# -# obj = self.pool.get('ir.model.fields') -# ids = obj.search(cr, uid, [('model_id','in',id)]) -# records = obj.read(cr, uid, ids) -# for record in records: -# id = record['id'] -# fld = field + '/' + record['name'] -# -# result.append((id, fld)) -# return result -# -# if not args: -# args=[] -# if not context: -# context={} -# return super(ir_model_fields, self).name_search(cr, uid, name, args, operator, context, limit) -# -# result = [] -# obj = self.pool.get('ir.model.fields') -# ids = obj.search(cr, uid, args) -# records = obj.read(cr, uid, ids) -# for record in records: -# id = record['id'] -# field = record['name'] -# -# if record['ttype'] == 'many2one': -# rel = record['relation'] -# res = get_fields(cr, uid, field, record['relation']) -# for rs in res: -# result.append(rs) -# -# result.append((id, field)) -# -# for rs in result: -# obj.write(cr, uid, [rs[0]], {'complete_name':rs[1]}) -# -# #result = super(ir_model_fields, self).name_search(cr, uid, name, [['complete_name','ilike',name]], operator, context, limit) -# return result -# -#ir_model_fields() +class ir_model_fields(osv.osv): + _inherit = 'ir.model.fields' + _rec_name = 'complete_name' + _columns = { + 'complete_name': fields.char('Complete Name', required=True, size=64, select=1), + } + + def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=80): + def get_fields(cr, uid, field, rel): + result = [] + mobj = self.pool.get('ir.model') + id = mobj.search(cr, uid, [('model','=',rel)]) + + obj = self.pool.get('ir.model.fields') + ids = obj.search(cr, uid, [('model_id','in',id)]) + records = obj.read(cr, uid, ids) + for record in records: + id = record['id'] + fld = field + '/' + record['name'] + + result.append((id, fld)) + return result + + if not args: + args=[] + if not context: + context={} + return super(ir_model_fields, self).name_search(cr, uid, name, args, operator, context, limit) + + result = [] + obj = self.pool.get('ir.model.fields') + ids = obj.search(cr, uid, args) + records = obj.read(cr, uid, ids) + for record in records: + id = record['id'] + field = record['name'] + + if record['ttype'] == 'many2one': + rel = record['relation'] + res = get_fields(cr, uid, field, record['relation']) + for rs in res: + result.append(rs) + + result.append((id, field)) + + for rs in result: + obj.write(cr, uid, [rs[0]], {'complete_name':rs[1]}) + + #result = super(ir_model_fields, self).name_search(cr, uid, name, [['complete_name','ilike',name]], operator, context, limit) + return result + +ir_model_fields() ## # Actions that are run on the server side # From 434d86e6ec0aaf87fde96077043ebb4c8dc661f3 Mon Sep 17 00:00:00 2001 From: "mga@tinyerp.com" <> Date: Wed, 20 Aug 2008 15:54:11 +0530 Subject: [PATCH 088/169] * bug for the searching in name_search method for the ir.model.fields bzr revid: mga@tinyerp.com-20080820102411-9m60qmn8s16hn0re --- bin/addons/base/ir/ir_actions.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index 4e65045ab29..1f63e33f054 100755 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -318,7 +318,12 @@ class ir_model_fields(osv.osv): for rs in result: obj.write(cr, uid, [rs[0]], {'complete_name':rs[1]}) - #result = super(ir_model_fields, self).name_search(cr, uid, name, [['complete_name','ilike',name]], operator, context, limit) + iids = [] + for rs in result: + iids.append(rs[0]) + + result = super(ir_model_fields, self).name_search(cr, uid, name, [('complete_name','ilike',name), ('id','in',iids)], operator, context, limit) + return result ir_model_fields() From 95518b629b7e81fc63b387a481687ddda2699f1a Mon Sep 17 00:00:00 2001 From: "mga@tinyerp.com" <> Date: Wed, 20 Aug 2008 16:02:48 +0530 Subject: [PATCH 089/169] bug fix for the server action perform name_search dynamic is context have the 'server_action' otherwise if context.get('key') != 'server_action': normal search bzr revid: mga@tinyerp.com-20080820103248-9pctdx2o07fn0kei --- bin/addons/base/ir/ir.xml | 2 +- bin/addons/base/ir/ir_actions.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/addons/base/ir/ir.xml b/bin/addons/base/ir/ir.xml index 83372789a86..72983b8fa2c 100755 --- a/bin/addons/base/ir/ir.xml +++ b/bin/addons/base/ir/ir.xml @@ -1091,7 +1091,7 @@ ir.actions.server form - {'key':'myval'} + {'key':'server_action'} diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index 1f63e33f054..a6107510e32 100755 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -299,6 +299,9 @@ class ir_model_fields(osv.osv): context={} return super(ir_model_fields, self).name_search(cr, uid, name, args, operator, context, limit) + if context.get('key') != 'server_action': + return super(ir_model_fields, self).name_search(cr, uid, name, args, operator, context, limit) + result = [] obj = self.pool.get('ir.model.fields') ids = obj.search(cr, uid, args) From 17f0040aae5e26cb132f4a7db08eb532d28f8dc2 Mon Sep 17 00:00:00 2001 From: Amit Mendapara Date: Wed, 20 Aug 2008 16:06:32 +0530 Subject: [PATCH 090/169] fixed processus module to work with custom addons path bzr revid: ame@tinyerp.com-20080820103632-x69sfyb2qfyp2epa --- addons/processus/processus_view.xml | 4 ++-- addons/processus/report/processus_print.py | 15 +++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/addons/processus/processus_view.xml b/addons/processus/processus_view.xml index 46033d4fb06..3eac5892874 100644 --- a/addons/processus/processus_view.xml +++ b/addons/processus/processus_view.xml @@ -92,7 +92,7 @@
- + @@ -134,7 +134,7 @@ - + diff --git a/addons/processus/report/processus_print.py b/addons/processus/report/processus_print.py index 6aa800cd218..f7f6b83fe40 100644 --- a/addons/processus/report/processus_print.py +++ b/addons/processus/report/processus_print.py @@ -4,10 +4,12 @@ import ImageDraw import ImageFont import math +import addons + ROUNDED = 30 BGCOLOR = (228,233,237) TITLECOLOR = (150,70,70) -FONT = 'addons/processus/report/sb.ttf' +FONT = addons.get_module_resource('processus', 'report/sb.ttf') BOXSIZE = (160,120) size = 800,600 @@ -127,24 +129,25 @@ class graph(object): def node(self, x, y, data, start_color=False): self.draw.node(x,y,BOXSIZE[0], BOXSIZE[1], data.get('title','Unknown'), start_color and (255,125,125) or BGCOLOR) - self.draw.picture(x+35, y+BOXSIZE[1]-5, 'addons/processus/report/gtk-help.png') + self.draw.picture(x+35, y+BOXSIZE[1]-5, addons.get_module_resource('processus', 'report/gtk-help.png')) if start_color: - self.draw.picture(x+65, y+BOXSIZE[1]-5, 'addons/processus/report/gtk-open.png') - self.draw.picture(x+95, y+BOXSIZE[1]-5, 'addons/processus/report/gtk-print.png') + self.draw.picture(x+65, y+BOXSIZE[1]-5, addons.get_module_resource('processus', 'report/gtk-open.png')) + self.draw.picture(x+95, y+BOXSIZE[1]-5, addons.get_module_resource('processus', 'report/gtk-print.png')) y = y+25 menus = data.get('menu','').split('/') while menus: menu = menus.pop(0) if menu: if menus: menu=menu+' /' - self.draw.draw_text(x+23, y, 10, menu, color=(0,0,0), maxlength=BOXSIZE[0] - 25, font_name='addons/processus/report/ds.ttf') + self.draw.draw_text(x+23, y, 10, menu, color=(0,0,0), maxlength=BOXSIZE[0] - 25, + font_name=addons.get_module_resource('processus', 'report/ds.ttf')) y+=15 def arrow_role(self, node_from, node_to, role='Hello'): start = (node_from[0]+BOXSIZE[0]/2, node_from[1]+BOXSIZE[1]/2) stop = (node_to[0]+BOXSIZE[0]/2, node_to[1]+BOXSIZE[1]/2) (x,y) = self.draw.arrow_role(start, stop, role) - self.draw.picture(x, y-3, 'addons/processus/report/role.png') + self.draw.picture(x, y-3, addons.get_module_resource('processus', 'report/role.png')) self.draw.draw_text(x,y-3, 12, 'Salesman', color=(0,0,0), center=True) def arrow(self, node_from, node_to): From 21d60c388980a3f3182af0ce515b323524f6e393 Mon Sep 17 00:00:00 2001 From: "mga@tinyerp.com" <> Date: Wed, 20 Aug 2008 16:45:20 +0530 Subject: [PATCH 091/169] * Add evaluation to get the email/sms addres from the object * Add new start up attribute to the server --smtp-port * Make change in the misc for the connecting method for smtp server bzr revid: mga@tinyerp.com-20080820111520-amoeqiec817agewa --- bin/addons/base/ir/ir.xml | 2 ++ bin/addons/base/ir/ir_actions.py | 25 +++++++++++++++---------- bin/tools/config.py | 5 ++++- bin/tools/misc.py | 2 +- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/bin/addons/base/ir/ir.xml b/bin/addons/base/ir/ir.xml index 72983b8fa2c..542daa77e75 100755 --- a/bin/addons/base/ir/ir.xml +++ b/bin/addons/base/ir/ir.xml @@ -1059,6 +1059,8 @@ + + diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index a6107510e32..9684ff98816 100755 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -330,6 +330,7 @@ class ir_model_fields(osv.osv): return result ir_model_fields() + ## # Actions that are run on the server side # @@ -379,11 +380,6 @@ class actions_server(osv.osv): """ } -# def on_change_model(self, cr, uid, ids, model_id): -# print '**************** : method called' -# res = {'value' : {'address': [('key1','Email Value One')]} } -# return res - # # Context should contains: # ids : original ids @@ -411,14 +407,23 @@ class actions_server(osv.osv): user = config['email_from'] subject = action.name - #TODO : Apply Mail merge in to the Content of the Email - print '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ',context + obj_pool = self.pool.get(action.model_id.model) + id = context.get('active_id') + obj = obj_pool.browse(cr, uid, id) + + fields = action.address.complete_name.split('/') + for field in fields: + obj = getattr(obj, field) + + address = obj body = action.message - if tools.email_send_attach(user, action.address, subject, body, debug=False) == True: - logger.notifyChannel('email', netsvc.LOG_INFO, 'Email successfully send to : %s' % (action.address)) + #TODO : Apply Mail merge in to the Content of the Email + + if tools.email_send_attach(user, address, subject, body, debug=True) == True: + logger.notifyChannel('email', netsvc.LOG_INFO, 'Email successfully send to : %s' % (address)) else: - logger.notifyChannel('email', netsvc.LOG_ERROR, 'Failed to send email to : %s' % (action.address)) + logger.notifyChannel('email', netsvc.LOG_ERROR, 'Failed to send email to : %s' % (address)) if action.state == 'trigger': wf_service = netsvc.LocalService("workflow") diff --git a/bin/tools/config.py b/bin/tools/config.py index fbe63161473..df01bf50e3e 100644 --- a/bin/tools/config.py +++ b/bin/tools/config.py @@ -64,6 +64,7 @@ class configmanager(object): 'secure': False, 'smtp_server': 'localhost', 'smtp_user': False, + 'smtp_port':25, 'smtp_password': False, 'stop_after_init': False, # this will stop the server after initialization 'price_accuracy': 2, @@ -96,8 +97,10 @@ class configmanager(object): parser.add_option('--debug', dest='debug_mode', action='store_true', default=False, help='enable debug mode') parser.add_option("--assert-exit-level", dest='assert_exit_level', help="specify the level at which a failed assertion will stop the server " + str(assert_exit_levels)) parser.add_option("-S", "--secure", dest="secure", action="store_true", help="launch server over https instead of http", default=False) + parser.add_option('--email-from', dest='email_from', default='', help='specify the SMTP email address for sending email') parser.add_option('--smtp', dest='smtp_server', default='', help='specify the SMTP server for sending email') + parser.add_option('--smtp-port', dest='smtp_port', default='25', help='specify the SMTP port') parser.add_option('--smtp-ssl', dest='smtp_ssl', default='', help='specify the SMTP server support SSL or not') parser.add_option('--smtp-user', dest='smtp_user', default='', help='specify the SMTP username for sending email') parser.add_option('--smtp-password', dest='smtp_password', default='', help='specify the SMTP password for sending email') @@ -156,7 +159,7 @@ class configmanager(object): self.options['pidfile'] = False for arg in ('interface', 'port', 'db_name', 'db_user', 'db_password', 'db_host', - 'db_port', 'logfile', 'pidfile', 'secure', 'smtp_ssl', 'email_from', 'smtp_server', 'smtp_user', 'smtp_password', 'price_accuracy', 'netinterface', 'netport', 'db_maxconn', 'import_partial', 'addons_path'): + 'db_port', 'logfile', 'pidfile', 'secure', 'smtp_ssl', 'smtp_port', 'email_from', 'smtp_server', 'smtp_user', 'smtp_password', 'price_accuracy', 'netinterface', 'netport', 'db_maxconn', 'import_partial', 'addons_path'): if getattr(opt, arg): self.options[arg] = getattr(opt, arg) diff --git a/bin/tools/misc.py b/bin/tools/misc.py index f82a44d8808..4447fed5b01 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -403,7 +403,7 @@ def email_send_attach(email_from, email_to, subject, body, email_cc=None, email_ s.starttls() s.ehlo() - s.connect(config['smtp_server']) + s.connect(config['smtp_server'], config['smtp_port']) if config['smtp_user'] or config['smtp_password']: s.login(config['smtp_user'], config['smtp_password']) s.sendmail(email_from, flatten([email_to, email_cc, email_bcc]), msg.as_string()) From 7764a2e4a85c13e75aa3b2ea257c7bb5f6671d24 Mon Sep 17 00:00:00 2001 From: "mga@tinyerp.com" <> Date: Wed, 20 Aug 2008 16:48:04 +0530 Subject: [PATCH 092/169] Make the same changes in to the email_send like email_send_attach Make email send debug to False in Server Action bzr revid: mga@tinyerp.com-20080820111804-qcrvx2n3o94yhq4u --- bin/addons/base/ir/ir_actions.py | 2 +- bin/tools/misc.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index 9684ff98816..806f129994f 100755 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -420,7 +420,7 @@ class actions_server(osv.osv): body = action.message #TODO : Apply Mail merge in to the Content of the Email - if tools.email_send_attach(user, address, subject, body, debug=True) == True: + if tools.email_send_attach(user, address, subject, body, debug=False) == True: logger.notifyChannel('email', netsvc.LOG_INFO, 'Email successfully send to : %s' % (address)) else: logger.notifyChannel('email', netsvc.LOG_ERROR, 'Failed to send email to : %s' % (address)) diff --git a/bin/tools/misc.py b/bin/tools/misc.py index 4447fed5b01..e02785efa18 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -338,7 +338,15 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non msg['Message-Id'] = '<'+str(time.time())+'-tinycrm-'+str(tinycrm)+'@'+socket.gethostname()+'>' try: s = smtplib.SMTP() - s.connect(config['smtp_server']) + + if debug: + s.debuglevel = 5 + if ssl: + s.ehlo() + s.starttls() + s.ehlo() + + s.connect(config['smtp_server'], config['smtp_port']) if config['smtp_user'] or config['smtp_password']: s.login(config['smtp_user'], config['smtp_password']) s.sendmail(email_from, flatten([email_to, email_cc, email_bcc]), msg.as_string()) From 75da15c3b9cea38c8415abc7f2ee450a4c1f6d86 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Wed, 20 Aug 2008 14:00:54 +0200 Subject: [PATCH 093/169] modules: * merge methods state_change and state_upgrade into state_update * raise an exception when there is an unmet dependency bzr revid: christophe@tinyerp.com-20080820120054-1l2fdexs1lty7nk8 --- bin/addons/base/module/module.py | 34 ++++++++++---------------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py index 548d2c65f10..f64c5503991 100644 --- a/bin/addons/base/module/module.py +++ b/bin/addons/base/module/module.py @@ -267,41 +267,27 @@ class module(osv.osv): _('You try to remove a module that is installed or will be installed')) return super(module, self).unlink(cr, uid, ids, context=context) - def state_change(self, cr, uid, ids, newstate, context={}, level=50): + def state_update(self, cr, uid, ids, newstate, states_to_update, context={}, level=50): if level<1: - raise Exception, _('Recursion error in modules dependencies !') + raise orm.except_orm(_('Error'), _('Recursion error in modules dependencies !')) demo = True for module in self.browse(cr, uid, ids): mdemo = True for dep in module.dependencies_id: - ids2 = self.search(cr, uid, [('name','=',dep.name)]) - mdemo = self.state_change(cr, uid, ids2, newstate, context, level-1,)\ - and mdemo + if dep.state == 'unknown': + raise orm.except_orm(_('Error'), _('Unmet dependency: %s') % (dep.name,)) + if dep.state != newstate: + ids2 = self.search(cr, uid, [('name','=',dep.name)]) + mdemo = self.state_update(cr, uid, ids2, newstate, states_to_update, context, level-1,) and mdemo if not module.dependencies_id: mdemo = module.demo - if module.state == 'uninstalled': + if module.state in states_to_update: self.write(cr, uid, [module.id], {'state': newstate, 'demo':mdemo}) demo = demo and mdemo return demo - def state_upgrade(self, cr, uid, ids, newstate, context=None, level=50): - dep_obj = self.pool.get('ir.module.module.dependency') - if level<1: - raise Exception, _('Recursion error in modules dependencies !') - for module in self.browse(cr, uid, ids): - dep_ids = dep_obj.search(cr, uid, [('name', '=', module.name)]) - if dep_ids: - ids2 = [] - for dep in dep_obj.browse(cr, uid, dep_ids): - if dep.module_id.state != 'to upgrade': - ids2.append(dep.module_id.id) - self.state_upgrade(cr, uid, ids2, newstate, context, level) - if module.state == 'installed': - self.write(cr, uid, module.id, {'state': newstate}) - return True - def button_install(self, cr, uid, ids, context={}): - return self.state_change(cr, uid, ids, 'to install', context) + return self.state_update(cr, uid, ids, 'to install', ['uninstalled'], context) def button_install_cancel(self, cr, uid, ids, context={}): self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False}) @@ -327,7 +313,7 @@ class module(osv.osv): self.write(cr, uid, ids, {'state': 'installed'}) return True def button_upgrade(self, cr, uid, ids, context=None): - return self.state_upgrade(cr, uid, ids, 'to upgrade', context) + return self.state_update(cr, uid, ids, 'to upgrade', ['installed'], context) def button_upgrade_cancel(self, cr, uid, ids, context={}): self.write(cr, uid, ids, {'state': 'installed'}) return True From 649c027c16e2506ffb122d374e5a68b5213e9815 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 20 Aug 2008 14:47:57 +0200 Subject: [PATCH 094/169] Bugfixed Partial Import bzr revid: fp@tinyerp.com-20080820124757-hq1zjhlskph54pva --- bin/osv/orm.py | 5 ++++- bin/tools/convert.py | 22 +++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 26da17e0d83..a34ad7fd23b 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -594,6 +594,9 @@ class orm_template(object): done = 0 initial_size = len(datas) + if config.get('import_partial', False) and filename: + data = pickle.load(file(config.get('import_partial'))) + original_value = data['files'].get(filename, 0) counter = 0 while len(datas): counter += 1 @@ -613,7 +616,7 @@ class orm_template(object): self.write(cr, uid, [id], translate[lang], context2) if config.get('import_partial', False) and filename and (not (counter%100)) : data = pickle.load(file(config.get('import_partial'))) - data[filename] = initial_size - len(datas) + data.get(filename, 0) + data['files'][filename] = initial_size - len(datas) + original_value pickle.dump(data, file(config.get('import_partial'),'wb')) cr.commit() diff --git a/bin/tools/convert.py b/bin/tools/convert.py index d1855e24e42..adc7591b698 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -729,17 +729,20 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', reader = csv.reader(input, quotechar='"', delimiter=',') fields = reader.next() + fname_partial = "" if config.get('import_partial'): fname_partial = module + '/'+ fname if not os.path.isfile(config.get('import_partial')): - pickle.dump({}, file(config.get('import_partial'),'w+')) - data = pickle.load(file(config.get('import_partial'))) - if fname_partial in data: - if not data[fname_partial]: - return - else: - for i in range(data[fname_partial]): - reader.next() + pickle.dump({'files':{},'ids':{module:idref}}, file(config.get('import_partial'),'w+')) + else: + data = pickle.load(file(config.get('import_partial'))) + if fname_partial in data['files']: + #idref.update(data['ids'][module]) + if not data['files'][fname_partial]: + return + else: + for i in range(data['files'][fname_partial]): + reader.next() if not (mode == 'init' or 'id' in fields): return @@ -754,7 +757,8 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', if config.get('import_partial'): data = pickle.load(file(config.get('import_partial'))) - data[fname_partial] = 0 + data['files'][fname_partial] = 0 + #data['ids'][module] = idref pickle.dump(data, file(config.get('import_partial'),'wb')) # From 4f112db1f1f399b87aa984d05949f61f44f14009 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 20 Aug 2008 14:51:19 +0200 Subject: [PATCH 095/169] Simplified Partial Import bzr revid: fp@tinyerp.com-20080820125119-fgnnp0s24s4lipr7 --- bin/osv/orm.py | 4 ++-- bin/tools/convert.py | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index a34ad7fd23b..9866c7a5008 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -596,7 +596,7 @@ class orm_template(object): initial_size = len(datas) if config.get('import_partial', False) and filename: data = pickle.load(file(config.get('import_partial'))) - original_value = data['files'].get(filename, 0) + original_value = data.get(filename, 0) counter = 0 while len(datas): counter += 1 @@ -616,7 +616,7 @@ class orm_template(object): self.write(cr, uid, [id], translate[lang], context2) if config.get('import_partial', False) and filename and (not (counter%100)) : data = pickle.load(file(config.get('import_partial'))) - data['files'][filename] = initial_size - len(datas) + original_value + data[filename] = initial_size - len(datas) + original_value pickle.dump(data, file(config.get('import_partial'),'wb')) cr.commit() diff --git a/bin/tools/convert.py b/bin/tools/convert.py index adc7591b698..7588ca2f1cc 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -733,15 +733,14 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', if config.get('import_partial'): fname_partial = module + '/'+ fname if not os.path.isfile(config.get('import_partial')): - pickle.dump({'files':{},'ids':{module:idref}}, file(config.get('import_partial'),'w+')) + pickle.dump({}, file(config.get('import_partial'),'w+')) else: data = pickle.load(file(config.get('import_partial'))) - if fname_partial in data['files']: - #idref.update(data['ids'][module]) - if not data['files'][fname_partial]: + if fname_partial in data: + if not data[fname_partial]: return else: - for i in range(data['files'][fname_partial]): + for i in range(data[fname_partial]): reader.next() if not (mode == 'init' or 'id' in fields): @@ -757,8 +756,7 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', if config.get('import_partial'): data = pickle.load(file(config.get('import_partial'))) - data['files'][fname_partial] = 0 - #data['ids'][module] = idref + data[fname_partial] = 0 pickle.dump(data, file(config.get('import_partial'),'wb')) # From 5dec4f32d389a4910e387375d65edcb3f4f9661f Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 20 Aug 2008 20:40:35 +0200 Subject: [PATCH 096/169] Bug lp:259807: Ergonomy improvement in module installation bzr revid: fp@tinyerp.com-20080820184035-tjw81796721aueht --- bin/addons/base/module/module_view.xml | 7 ++----- bin/addons/base/module/module_wizard.xml | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/bin/addons/base/module/module_view.xml b/bin/addons/base/module/module_view.xml index 1aef6ddc923..5b14c59e9f0 100644 --- a/bin/addons/base/module/module_view.xml +++ b/bin/addons/base/module/module_view.xml @@ -79,15 +79,12 @@ -