From c57e613c0a6423e1a2447b5258cfc0deac7d157a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Thu, 22 Sep 2011 13:07:02 -0300 Subject: [PATCH 01/36] [IMP] sale: refactored action_ship_create to: 1) enable created objects like picking, move and procurement to take take their creation context into account in potential overriders 2) enable to partion the moves over different pickings whenever required by simply overriding a key generation bzr revid: rvalyi@gmail.com-20110922160702-r0h48975vxbx1by0 --- addons/sale/sale.py | 73 +++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 12c27c197ed..ca3775848ea 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -665,41 +665,64 @@ class sale_order(osv.osv): return False return canceled + def _get_order_picking_key(self, cr, uid, order, order_data, order_line, pickings_dict, context=None): + """Allows to partition a sale over several pickings: just override it and return + a unique key for the given parameters. If key changes, a new picking will be created. + :params order: the sale order + :params order_data: a place holder to store data while iterating the order lines + :params line: the sale order line + :params pickings_dict: the current dictionary of key / picking_id for this order + :return: unique key for those parameters + :rtype: string + """ + return "default" + + def _create_order_picking(self, cr, uid, order, order_data, line, picking_dict, picking_vals, context=None): + picking_vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out') + return self.pool.get('stock.picking').create(cr, uid, picking_vals) + + def _create_line_move(self, cr, uid, order, order_data, line, move_vals, context=None): + return self.pool.get('stock.move').create(cr, uid, move_vals) + + def _create_line_procurement(self, cr, uid, order, order_data, line, move_id, proc_vals, context=None): + return self.pool.get('procurement.order').create(cr, uid, proc_vals) + def action_ship_create(self, cr, uid, ids, *args): wf_service = netsvc.LocalService("workflow") - picking_id = False move_obj = self.pool.get('stock.move') proc_obj = self.pool.get('procurement.order') - company = self.pool.get('res.users').browse(cr, uid, uid).company_id for order in self.browse(cr, uid, ids, context={}): - proc_ids = [] output_id = order.shop_id.warehouse_id.lot_output_id.id - picking_id = False + proc_ids = [] + pickings = {} + order_data = {} #a place holder to store data while iteraing the lines if needed + for line in order.order_line: - proc_id = False date_planned = datetime.strptime(order.date_order, '%Y-%m-%d') + relativedelta(days=line.delay or 0.0) - date_planned = (date_planned - timedelta(days=company.security_lead)).strftime('%Y-%m-%d %H:%M:%S') + date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime('%Y-%m-%d %H:%M:%S') if line.state == 'done': continue move_id = False if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'): location_id = order.shop_id.warehouse_id.lot_stock_id.id + picking_key = self._get_order_picking_key(cr, uid, order, order_data, line, pickings, *args) + picking_id = pickings.get(picking_key, False) if not picking_id: - pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out') - picking_id = self.pool.get('stock.picking').create(cr, uid, { - 'name': pick_name, - 'origin': order.name, - 'type': 'out', - 'state': 'auto', - 'move_type': order.picking_policy, - 'sale_id': order.id, - 'address_id': order.partner_shipping_id.id, - 'note': order.note, - 'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none', - 'company_id': order.company_id.id, - }) - move_id = self.pool.get('stock.move').create(cr, uid, { + picking_id = self._create_order_picking(cr, uid, order, order_data, line, pickings, { + 'origin': order.name, + 'type': 'out', + 'state': 'auto', + 'move_type': order.picking_policy, + 'sale_id': order.id, + 'address_id': order.partner_shipping_id.id, + 'note': order.note, + 'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none', + 'company_id': order.company_id.id, + }, *args) + pickings[picking_key] = picking_id + + move_id = self._create_line_move(cr, uid, order, order_data, line, { 'name': line.name[:64], 'picking_id': picking_id, 'product_id': line.product_id.id, @@ -717,13 +740,12 @@ class sale_order(osv.osv): 'sale_line_id': line.id, 'tracking_id': False, 'state': 'draft', - #'state': 'waiting', 'note': line.notes, 'company_id': order.company_id.id, - }) + }, *args) if line.product_id: - proc_id = self.pool.get('procurement.order').create(cr, uid, { + proc_id = self._create_line_procurement(cr, uid, order, order_data, line, move_id, { 'name': line.name, 'origin': order.name, 'date_planned': date_planned, @@ -739,8 +761,7 @@ class sale_order(osv.osv): 'move_id': move_id, 'property_ids': [(6, 0, [x.id for x in line.property_ids])], 'company_id': order.company_id.id, - 'sale_line_id': line.id, - }) + }, *args) proc_ids.append(proc_id) self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id}) if order.state == 'shipping_except': @@ -755,7 +776,7 @@ class sale_order(osv.osv): val = {} - if picking_id: + for k, picking_id in pickings.items(): wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr) for proc_id in proc_ids: From d56c6a1e40dc49c570997278d58e9a9425d3508b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Thu, 22 Sep 2011 13:31:31 -0300 Subject: [PATCH 02/36] [FIX] sale: forgot sale_line_id field in procurement generation in previous refactoring bzr revid: rvalyi@gmail.com-20110922163131-c76u3dg2afrahh16 --- addons/sale/sale.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index ca3775848ea..86c62b44a69 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -761,6 +761,7 @@ class sale_order(osv.osv): 'move_id': move_id, 'property_ids': [(6, 0, [x.id for x in line.property_ids])], 'company_id': order.company_id.id, + 'sale_line_id': line.id, }, *args) proc_ids.append(proc_id) self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id}) From ff43f2198a8215ac63454ebed86f2538fe30daad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sun, 2 Oct 2011 14:39:52 -0300 Subject: [PATCH 03/36] [REF] sale: totally reworked the action_ship_create modularization: - action_ship_create now calls into a a method allowing to partition the picking - picking, moves and procurements can be customized BEFORE they are created into the database - there is no empty method - I commented in the shipment exception management as it looks buggy. I just reproduced the original code however bzr revid: rvalyi@gmail.com-20111002173952-xlcmt6u1pgj6k9k4 --- addons/sale/sale.py | 238 +++++++++++++++++++++----------------------- 1 file changed, 115 insertions(+), 123 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index fc702181206..3e7e58c72c6 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -673,135 +673,127 @@ class sale_order(osv.osv): return False return canceled - def _get_order_picking_key(self, cr, uid, order, order_data, order_line, pickings_dict, context=None): - """Allows to partition a sale over several pickings: just override it and return - a unique key for the given parameters. If key changes, a new picking will be created. - :params order: the sale order - :params order_data: a place holder to store data while iterating the order lines - :params line: the sale order line - :params pickings_dict: the current dictionary of key / picking_id for this order - :return: unique key for those parameters - :rtype: string - """ - return "default" - def _create_order_picking(self, cr, uid, order, order_data, line, picking_dict, picking_vals, context=None): - picking_vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out') - return self.pool.get('stock.picking').create(cr, uid, picking_vals) + def _prepare_proc(self, cr, uid, order, line, move_id, date_planned, *args): + return { + 'name': line.name, + 'origin': order.name, + 'date_planned': date_planned, + 'product_id': line.product_id.id, + 'product_qty': line.product_uom_qty, + 'product_uom': line.product_uom.id, + 'product_uos_qty': (line.product_uos and line.product_uos_qty)\ + or line.product_uom_qty, + 'product_uos': (line.product_uos and line.product_uos.id)\ + or line.product_uom.id, + 'location_id': order.shop_id.warehouse_id.lot_stock_id.id, + 'procure_method': line.type, + 'move_id': move_id, + 'property_ids': [(6, 0, [x.id for x in line.property_ids])], + 'company_id': order.company_id.id, + 'sale_line_id': line.id, + } - def _create_line_move(self, cr, uid, order, order_data, line, move_vals, context=None): - return self.pool.get('stock.move').create(cr, uid, move_vals) - def _create_line_procurement(self, cr, uid, order, order_data, line, move_id, proc_vals, context=None): - return self.pool.get('procurement.order').create(cr, uid, proc_vals) + def _prepare_move(self, cr, uid, order, line, picking_id, date_planned, *args): + location_id = order.shop_id.warehouse_id.lot_stock_id.id + output_id = order.shop_id.warehouse_id.lot_output_id.id + return { + 'name': line.name[:64], + 'picking_id': picking_id, + 'product_id': line.product_id.id, + 'date': date_planned, + 'date_expected': date_planned, + 'product_qty': line.product_uom_qty, + 'product_uom': line.product_uom.id, + 'product_uos_qty': line.product_uos_qty, + 'product_uos': (line.product_uos and line.product_uos.id)\ + or line.product_uom.id, + 'product_packaging': line.product_packaging.id, + 'address_id': line.address_allotment_id.id or order.partner_shipping_id.id, + 'location_id': location_id, + 'location_dest_id': output_id, + 'sale_line_id': line.id, + 'tracking_id': False, + 'state': 'draft', + #'state': 'waiting', + 'note': line.notes, + 'company_id': order.company_id.id, + 'price_unit': line.product_id.standard_price or 0.0 + } + + + + def _prepare_picking(self, cr, uid, order, *args): + pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out') + return { + 'name': pick_name, + 'origin': order.name, + 'type': 'out', + 'state': 'auto', + 'move_type': order.picking_policy, + 'sale_id': order.id, + 'address_id': order.partner_shipping_id.id, + 'note': order.note, + 'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none', + 'company_id': order.company_id.id, + } + + + def _create_pickings_an_procurements(self, cr, uid, order, order_lines, picking_id=False, *args): + proc_ids = [] + for line in order_lines: + if line.state == 'done': + continue + + date_planned = datetime.strptime(order.date_order, '%Y-%m-%d') + relativedelta(days=line.delay or 0.0) + date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime('%Y-%m-%d %H:%M:%S') + + if line.product_id: + if line.product_id.product_tmpl_id.type in ('product', 'consu'): + if not picking_id: + picking_id = self.pool.get('stock.picking').create(cr, uid, self._prepare_picking(cr, uid, order, args)) + move_id = self.pool.get('stock.move').create(cr, uid, self._prepare_move(cr, uid, order, line, picking_id, date_planned, args)) + else: #a service has no stock move + move_id = False + + proc_id = self.pool.get('procurement.order').create(cr, uid, self._prepare_proc(cr, uid, order, line, move_id, date_planned, args)) + proc_ids.append(proc_id) + + if order.state == 'shipping_except': #deals with potentially cancelled shipments FIXME seems broken, see below + for pick in order.picking_ids: + for move in pick.move_lines: + if move.state == 'cancel': + mov_ids = self.pool.get('stock.move').search(cr, uid, [('state', '=', 'cancel'),('sale_line_id', '=', line.id),('picking_id', '=', pick.id)]) + if mov_ids: + for mov in move_obj.browse(cr, uid, mov_ids): + #FIXME: the following seems broken: what if move_id doesn't exist? What if there are several mov_ids? Shouldn't that be a sum? + self.pool.get('stock.move').write(cr, uid, [move_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty}) + self.pool.get('procurement.order').write(cr, uid, [proc_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty}) + + wf_service = netsvc.LocalService("workflow") + if picking_id: + wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr) + + for proc_id in proc_ids: + wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr) + + val = {} + if order.state == 'shipping_except': + val['state'] = 'progress' + val['shipped'] = False + + if (order.order_policy == 'manual'): + for line in order.order_line: + if (not line.invoiced) and (line.state not in ('cancel', 'draft')): + val['state'] = 'manual' + break + self.write(cr, uid, [order.id], val) + return True def action_ship_create(self, cr, uid, ids, *args): - wf_service = netsvc.LocalService("workflow") - move_obj = self.pool.get('stock.move') - proc_obj = self.pool.get('procurement.order') for order in self.browse(cr, uid, ids, context={}): - output_id = order.shop_id.warehouse_id.lot_output_id.id - proc_ids = [] - pickings = {} - order_data = {} #a place holder to store data while iteraing the lines if needed - - for line in order.order_line: - date_planned = datetime.strptime(order.date_order, '%Y-%m-%d') + relativedelta(days=line.delay or 0.0) - date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime('%Y-%m-%d %H:%M:%S') - - if line.state == 'done': - continue - move_id = False - if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'): - location_id = order.shop_id.warehouse_id.lot_stock_id.id - picking_key = self._get_order_picking_key(cr, uid, order, order_data, line, pickings, *args) - picking_id = pickings.get(picking_key, False) - if not picking_id: - picking_id = self._create_order_picking(cr, uid, order, order_data, line, pickings, { - 'origin': order.name, - 'type': 'out', - 'state': 'auto', - 'move_type': order.picking_policy, - 'sale_id': order.id, - 'address_id': order.partner_shipping_id.id, - 'note': order.note, - 'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none', - 'company_id': order.company_id.id, - }, *args) - pickings[picking_key] = picking_id - - move_id = self._create_line_move(cr, uid, order, order_data, line, { - 'name': line.name[:64], - 'picking_id': picking_id, - 'product_id': line.product_id.id, - 'date': date_planned, - 'date_expected': date_planned, - 'product_qty': line.product_uom_qty, - 'product_uom': line.product_uom.id, - 'product_uos_qty': line.product_uos_qty, - 'product_uos': (line.product_uos and line.product_uos.id)\ - or line.product_uom.id, - 'product_packaging': line.product_packaging.id, - 'address_id': line.address_allotment_id.id or order.partner_shipping_id.id, - 'location_id': location_id, - 'location_dest_id': output_id, - 'sale_line_id': line.id, - 'tracking_id': False, - 'state': 'draft', - 'note': line.notes, - 'company_id': order.company_id.id, - 'price_unit': line.product_id.standard_price or 0.0 - }, *args) - - if line.product_id: - proc_id = self._create_line_procurement(cr, uid, order, order_data, line, move_id, { - 'name': line.name, - 'origin': order.name, - 'date_planned': date_planned, - 'product_id': line.product_id.id, - 'product_qty': line.product_uom_qty, - 'product_uom': line.product_uom.id, - 'product_uos_qty': (line.product_uos and line.product_uos_qty)\ - or line.product_uom_qty, - 'product_uos': (line.product_uos and line.product_uos.id)\ - or line.product_uom.id, - 'location_id': order.shop_id.warehouse_id.lot_stock_id.id, - 'procure_method': line.type, - 'move_id': move_id, - 'property_ids': [(6, 0, [x.id for x in line.property_ids])], - 'company_id': order.company_id.id, - 'sale_line_id': line.id, - }, *args) - proc_ids.append(proc_id) - self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id}) - if order.state == 'shipping_except': - for pick in order.picking_ids: - for move in pick.move_lines: - if move.state == 'cancel': - mov_ids = move_obj.search(cr, uid, [('state', '=', 'cancel'),('sale_line_id', '=', line.id),('picking_id', '=', pick.id)]) - if mov_ids: - for mov in move_obj.browse(cr, uid, mov_ids): - move_obj.write(cr, uid, [move_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty}) - proc_obj.write(cr, uid, [proc_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty}) - - val = {} - - for k, picking_id in pickings.items(): - wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr) - - for proc_id in proc_ids: - wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr) - - if order.state == 'shipping_except': - val['state'] = 'progress' - val['shipped'] = False - - if (order.order_policy == 'manual'): - for line in order.order_line: - if (not line.invoiced) and (line.state not in ('cancel', 'draft')): - val['state'] = 'manual' - break - self.write(cr, uid, [order.id], val) + self._create_pickings_an_procurements(cr, uid, order, [line for line in order.order_line], False, args) return True def action_ship_end(self, cr, uid, ids, context=None): From ec101c5a357012c37e36e683578e012176795086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 3 Oct 2011 11:25:54 -0300 Subject: [PATCH 04/36] [REF] sale: more explicit method names, added docstring on main non trivial method bzr revid: rvalyi@gmail.com-20111003142554-yest0alzq00h83b7 --- addons/sale/sale.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 3e7e58c72c6..8cf54f4057e 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -673,8 +673,7 @@ class sale_order(osv.osv): return False return canceled - - def _prepare_proc(self, cr, uid, order, line, move_id, date_planned, *args): + def _prepare_order_line_procurement(self, cr, uid, order, line, move_id, date_planned, *args): return { 'name': line.name, 'origin': order.name, @@ -694,8 +693,7 @@ class sale_order(osv.osv): 'sale_line_id': line.id, } - - def _prepare_move(self, cr, uid, order, line, picking_id, date_planned, *args): + def _prepare_order_line_move(self, cr, uid, order, line, picking_id, date_planned, *args): location_id = order.shop_id.warehouse_id.lot_stock_id.id output_id = order.shop_id.warehouse_id.lot_output_id.id return { @@ -722,9 +720,7 @@ class sale_order(osv.osv): 'price_unit': line.product_id.standard_price or 0.0 } - - - def _prepare_picking(self, cr, uid, order, *args): + def _prepare_order_picking(self, cr, uid, order, *args): pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out') return { 'name': pick_name, @@ -739,8 +735,18 @@ class sale_order(osv.osv): 'company_id': order.company_id.id, } - def _create_pickings_an_procurements(self, cr, uid, order, order_lines, picking_id=False, *args): + """ + Create pickings and procurements for given order lines. Filtering the order lines allows to partition the delivery + over several pickings. + + :param cr: database cursor + :param uid: current user id + :param order: sale order object + :param order_lines: sale order line objects + :param picking_id: id of picking to use evenually + :return: True + """ proc_ids = [] for line in order_lines: if line.state == 'done': @@ -752,12 +758,12 @@ class sale_order(osv.osv): if line.product_id: if line.product_id.product_tmpl_id.type in ('product', 'consu'): if not picking_id: - picking_id = self.pool.get('stock.picking').create(cr, uid, self._prepare_picking(cr, uid, order, args)) - move_id = self.pool.get('stock.move').create(cr, uid, self._prepare_move(cr, uid, order, line, picking_id, date_planned, args)) + picking_id = self.pool.get('stock.picking').create(cr, uid, self._prepare_order_picking(cr, uid, order, args)) + move_id = self.pool.get('stock.move').create(cr, uid, self._prepare_order_line_move(cr, uid, order, line, picking_id, date_planned, args)) else: #a service has no stock move move_id = False - proc_id = self.pool.get('procurement.order').create(cr, uid, self._prepare_proc(cr, uid, order, line, move_id, date_planned, args)) + proc_id = self.pool.get('procurement.order').create(cr, uid, self._prepare_order_line_procurement(cr, uid, order, line, move_id, date_planned, args)) proc_ids.append(proc_id) if order.state == 'shipping_except': #deals with potentially cancelled shipments FIXME seems broken, see below From ed023b8f81baf3cbcbdff358723f56699580af4d Mon Sep 17 00:00:00 2001 From: "Yogesh (OpenERP)" Date: Tue, 4 Oct 2011 11:31:17 +0530 Subject: [PATCH 05/36] [FIX] show all configuration wizard in administrator dashboard. lp bug: https://launchpad.net/bugs/865162 fixed bzr revid: ysa@tinyerp.com-20111004060117-bylyhk4ye781q33r --- addons/web_dashboard/static/src/js/dashboard.js | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/web_dashboard/static/src/js/dashboard.js b/addons/web_dashboard/static/src/js/dashboard.js index 4672492821d..808fe64e5c7 100644 --- a/addons/web_dashboard/static/src/js/dashboard.js +++ b/addons/web_dashboard/static/src/js/dashboard.js @@ -292,7 +292,6 @@ openerp.web_dashboard.ConfigOverview = openerp.web.View.extend({ this._super(parent); this.dataset = new openerp.web.DataSetSearch( this, 'ir.actions.todo'); - this.dataset.domain = [['type', '=', 'manual']]; }, start: function () { this._super(); From 3348916e9c1027a799a4b3d962b52f96e881c0ba Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Tue, 4 Oct 2011 18:28:41 +0200 Subject: [PATCH 06/36] [fix] remove bad undefined value returned instead of a real promise. bzr revid: valentin.lab@kalysto.org-20111004162841-h25ns7b4ivxo43z3 --- addons/web/static/src/js/view_form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 1b7a7fe6fcf..cac9ad41e2c 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -808,7 +808,7 @@ openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({ page.id = self.pages[index].element_id; }); this.$element.tabs(); - this.view.on_button_new.add_last(this.do_select_first_visible_tab); + this.view.on_button_new.add_first(this.do_select_first_visible_tab); }, do_select_first_visible_tab: function() { for (var i = 0; i < this.pages.length; i++) { From d5accc0faea908edf758a4da22bcd4bc01485ca2 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 10 Oct 2011 15:51:57 +0200 Subject: [PATCH 07/36] [ADD] method to correctly serialize o2m commands into a sequence of record-dicts for e.g. onchanges also alters res.partner.bank _default_value to use it with res.partner addresses, and adds a few tests to ensure behavior is correct bzr revid: xmo@openerp.com-20111010135157-dafgcwwcni5gkeom --- openerp/addons/base/res/res_bank.py | 10 +-- openerp/osv/orm.py | 44 ++++++++++ tests/test_orm.py | 121 ++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 tests/test_orm.py diff --git a/openerp/addons/base/res/res_bank.py b/openerp/addons/base/res/res_bank.py index db92e220c64..5e96a7ecc04 100644 --- a/openerp/addons/base/res/res_bank.py +++ b/openerp/addons/base/res/res_bank.py @@ -108,14 +108,8 @@ class res_partner_bank(osv.osv): if not context.get('address'): return value - for _, id, address_data in context['address']: - if not (id or address): continue - address = {} - if id: - address.update(self.pool['res.partner.address'] - .read(cursor, user, [id], ['type', field], context=context)[0]) - if address_data: - address.update(address_data) + for address in self.pool.get('res.partner').serialize_o2m_commands( + cursor, user, 'address', context['address'], ['type', field], context=context): if address.get('type') == 'default': return address.get(field, value) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 3a3155ed7f1..07329a301fd 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4762,6 +4762,50 @@ class BaseModel(object): return True + def serialize_o2m_commands(self, cr, uid, field_name, o2m_commands, fields=None, context=None): + """ Serializes o2m commands into record dictionaries (as if + all the o2m records came from the database via a read()), and + returns an iterator over these dictionaries. + + Because o2m commands might be creation commands, not all + record ids will contain an ``id`` field. Commands matching an + existing record (UPDATE and LINK_TO) will have an id. + + :param field_name: name of the o2m field matching the commands + :type field_name: str + :param o2m_commands: one2many commands to execute on ``field_name`` + :type o2m_commands: list((int|False, int|False, dict|False)) + :param fields: list of fields to read from the database, when applicable + :type fields: list(str) + :param context: request context + :returns: o2m records in a shape similar to that returned by + ``read()`` (except records may be missing the ``id`` + field if they don't exist in db) + :rtype: iter(dict) + """ + o2m_model = self._all_columns[field_name].column._obj + + # extract and handle case of single ids (instead of commands): + # convert to LINK_TO commands (4) + c1, c2 = itertools.tee(o2m_commands) + commands = list(itertools.chain( + (command for command in c1 if isinstance(command, (list, tuple))), + ((4, id, None) for id in c2 if not isinstance(id, (list, tuple))))) + + # extract records to read, by id, in a mapping dict + ids_to_read = [id for (command, id, _) in commands if command in (1, 4)] + records_by_id = dict( + (record['id'], record) + for record in self.pool.get(o2m_model).read( + cr, uid, ids_to_read, fields=fields, context=context)) + + # merge record from db with record provided by command + for command, id, record in commands: + item = {} + if command in (1, 4): item.update(records_by_id[id]) + if command in (0, 1): item.update(record) + yield item + # keep this import here, at top it will cause dependency cycle errors import expression diff --git a/tests/test_orm.py b/tests/test_orm.py new file mode 100644 index 00000000000..67b97b894d6 --- /dev/null +++ b/tests/test_orm.py @@ -0,0 +1,121 @@ +import os +import unittest2 +import openerp + +UID = 1 +DB = os.environ['OPENERP_DATABASE'] + +CREATE = lambda values: (0, False, values) +UPDATE = lambda id, values: (1, id, values) +LINK_TO = lambda id: (4, id, False) + + +def setUpModule(): + openerp.tools.config['addons_path'] = os.environ['OPENERP_ADDONS_PATH'] + +class TestO2MSerialization(unittest2.TestCase): + def setUp(self): + self.cr = openerp.modules.registry.RegistryManager.get(DB).db.cursor() + self.partner = openerp.modules.registry.RegistryManager.get(DB)['res.partner'] + self.address = openerp.modules.registry.RegistryManager.get(DB)['res.partner.address'] + def tearDown(self): + self.cr.rollback() + self.cr.close() + + def test_no_command(self): + " empty list of commands yields an empty list of records " + results = list(self.partner.serialize_o2m_commands(self.cr, UID, 'address', [])) + + self.assertEqual(results, []) + + def test_CREATE_commands(self): + " returns the VALUES dict as-is " + results = list(self.partner.serialize_o2m_commands( + self.cr, UID, 'address', + map(CREATE, [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}]))) + self.assertEqual(results, [ + {'foo': 'bar'}, + {'foo': 'baz'}, + {'foo': 'baq'} + ]) + + def test_LINK_TO_command(self): + " reads the records from the database, records are returned with their ids. " + ids = [ + self.address.create(self.cr, UID, {'name': 'foo'}), + self.address.create(self.cr, UID, {'name': 'bar'}), + self.address.create(self.cr, UID, {'name': 'baz'}) + ] + commands = map(LINK_TO, ids) + + results = list(self.partner.serialize_o2m_commands(self.cr, UID, 'address', commands, ['name'])) + + self.assertEqual(results, [ + {'id': ids[0], 'name': 'foo'}, + {'id': ids[1], 'name': 'bar'}, + {'id': ids[2], 'name': 'baz'} + ]) + + def test_bare_ids_command(self): + " same as the equivalent LINK_TO commands " + ids = [ + self.address.create(self.cr, UID, {'name': 'foo'}), + self.address.create(self.cr, UID, {'name': 'bar'}), + self.address.create(self.cr, UID, {'name': 'baz'}) + ] + + results = list(self.partner.serialize_o2m_commands(self.cr, UID, 'address', ids, ['name'])) + + self.assertEqual(results, [ + {'id': ids[0], 'name': 'foo'}, + {'id': ids[1], 'name': 'bar'}, + {'id': ids[2], 'name': 'baz'} + ]) + + def test_UPDATE_command(self): + " take the in-db records and merge the provided information in " + id_foo = self.address.create(self.cr, UID, {'name': 'foo'}) + id_bar = self.address.create(self.cr, UID, {'name': 'bar'}) + id_baz = self.address.create(self.cr, UID, {'name': 'baz', 'city': 'tag'}) + + results = list(self.partner.serialize_o2m_commands( + self.cr, UID, 'address', [ + LINK_TO(id_foo), + UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}), + UPDATE(id_baz, {'name': 'quux'}) + ], ['name', 'city'])) + + self.assertEqual(results, [ + {'id': id_foo, 'name': 'foo', 'city': False}, + {'id': id_bar, 'name': 'qux', 'city': 'tagtag'}, + {'id': id_baz, 'name': 'quux', 'city': 'tag'} + ]) + + def test_mixed_commands(self): + ids = [ + self.address.create(self.cr, UID, {'name': name}) + for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply'] + ] + + results = list(self.partner.serialize_o2m_commands( + self.cr, UID, 'address', [ + CREATE({'name': 'foo'}), + UPDATE(ids[0], {'name': 'bar'}), + LINK_TO(ids[1]), + LINK_TO(ids[2]), + UPDATE(ids[3], {'name': 'quux',}), + UPDATE(ids[4], {'name': 'corge'}), + CREATE({'name': 'grault'}), + LINK_TO(ids[5]) + ], ['name'])) + + self.assertEqual(results, [ + {'name': 'foo'}, + {'id': ids[0], 'name': 'bar'}, + {'id': ids[1], 'name': 'baz'}, + {'id': ids[2], 'name': 'qux'}, + {'id': ids[3], 'name': 'quux'}, + {'id': ids[4], 'name': 'corge'}, + {'name': 'grault'}, + {'id': ids[5], 'name': 'garply'} + ]) From b5ee13a8450b7429dd4235507ed6f44def581baf Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 11 Oct 2011 10:03:38 +0200 Subject: [PATCH 08/36] [IMP] rename serialize_o2m_commands to resolve_o2m_commands_to_record_dicts bzr revid: xmo@openerp.com-20111011080338-hup42vivlvk4dly0 --- openerp/addons/base/res/res_bank.py | 2 +- openerp/osv/orm.py | 2 +- tests/test_orm.py | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openerp/addons/base/res/res_bank.py b/openerp/addons/base/res/res_bank.py index 5e96a7ecc04..ce1cd1c6a24 100644 --- a/openerp/addons/base/res/res_bank.py +++ b/openerp/addons/base/res/res_bank.py @@ -108,7 +108,7 @@ class res_partner_bank(osv.osv): if not context.get('address'): return value - for address in self.pool.get('res.partner').serialize_o2m_commands( + for address in self.pool.get('res.partner').resolve_o2m_commands_to_record_dicts( cursor, user, 'address', context['address'], ['type', field], context=context): if address.get('type') == 'default': diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 07329a301fd..a17eb49e8b3 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4762,7 +4762,7 @@ class BaseModel(object): return True - def serialize_o2m_commands(self, cr, uid, field_name, o2m_commands, fields=None, context=None): + def resolve_o2m_commands_to_record_dicts(self, cr, uid, field_name, o2m_commands, fields=None, context=None): """ Serializes o2m commands into record dictionaries (as if all the o2m records came from the database via a read()), and returns an iterator over these dictionaries. diff --git a/tests/test_orm.py b/tests/test_orm.py index 67b97b894d6..365ac95ee4d 100644 --- a/tests/test_orm.py +++ b/tests/test_orm.py @@ -24,13 +24,13 @@ class TestO2MSerialization(unittest2.TestCase): def test_no_command(self): " empty list of commands yields an empty list of records " - results = list(self.partner.serialize_o2m_commands(self.cr, UID, 'address', [])) + results = list(self.partner.resolve_o2m_commands_to_record_dicts(self.cr, UID, 'address', [])) self.assertEqual(results, []) def test_CREATE_commands(self): " returns the VALUES dict as-is " - results = list(self.partner.serialize_o2m_commands( + results = list(self.partner.resolve_o2m_commands_to_record_dicts( self.cr, UID, 'address', map(CREATE, [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}]))) self.assertEqual(results, [ @@ -48,7 +48,7 @@ class TestO2MSerialization(unittest2.TestCase): ] commands = map(LINK_TO, ids) - results = list(self.partner.serialize_o2m_commands(self.cr, UID, 'address', commands, ['name'])) + results = list(self.partner.resolve_o2m_commands_to_record_dicts(self.cr, UID, 'address', commands, ['name'])) self.assertEqual(results, [ {'id': ids[0], 'name': 'foo'}, @@ -64,7 +64,7 @@ class TestO2MSerialization(unittest2.TestCase): self.address.create(self.cr, UID, {'name': 'baz'}) ] - results = list(self.partner.serialize_o2m_commands(self.cr, UID, 'address', ids, ['name'])) + results = list(self.partner.resolve_o2m_commands_to_record_dicts(self.cr, UID, 'address', ids, ['name'])) self.assertEqual(results, [ {'id': ids[0], 'name': 'foo'}, @@ -78,7 +78,7 @@ class TestO2MSerialization(unittest2.TestCase): id_bar = self.address.create(self.cr, UID, {'name': 'bar'}) id_baz = self.address.create(self.cr, UID, {'name': 'baz', 'city': 'tag'}) - results = list(self.partner.serialize_o2m_commands( + results = list(self.partner.resolve_o2m_commands_to_record_dicts( self.cr, UID, 'address', [ LINK_TO(id_foo), UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}), @@ -97,7 +97,7 @@ class TestO2MSerialization(unittest2.TestCase): for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply'] ] - results = list(self.partner.serialize_o2m_commands( + results = list(self.partner.resolve_o2m_commands_to_record_dicts( self.cr, UID, 'address', [ CREATE({'name': 'foo'}), UPDATE(ids[0], {'name': 'bar'}), From 4d0a189796e645003e55ac82b0804fce5fb54ae0 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 11 Oct 2011 10:12:02 +0200 Subject: [PATCH 09/36] [IMP] handle case where o2m commands are pairs instead of triplets bzr revid: xmo@openerp.com-20111011081202-y1ou74u038o2kl58 --- openerp/osv/orm.py | 16 ++++++++++------ tests/test_orm.py | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index a17eb49e8b3..7cd2cbc22c5 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4785,12 +4785,16 @@ class BaseModel(object): """ o2m_model = self._all_columns[field_name].column._obj - # extract and handle case of single ids (instead of commands): - # convert to LINK_TO commands (4) - c1, c2 = itertools.tee(o2m_commands) - commands = list(itertools.chain( - (command for command in c1 if isinstance(command, (list, tuple))), - ((4, id, None) for id in c2 if not isinstance(id, (list, tuple))))) + # convert single ids and pairs to tripled commands + commands = [] + for o2m_command in o2m_commands: + if not isinstance(o2m_command, (list, tuple)): + commands.append((4, o2m_command, False)) + elif len(o2m_command) == 2: + command, id = o2m_command + commands.append((command, id, False)) + else: + commands.append(o2m_command) # extract records to read, by id, in a mapping dict ids_to_read = [id for (command, id, _) in commands if command in (1, 4)] diff --git a/tests/test_orm.py b/tests/test_orm.py index 365ac95ee4d..ff82b779cd9 100644 --- a/tests/test_orm.py +++ b/tests/test_orm.py @@ -119,3 +119,21 @@ class TestO2MSerialization(unittest2.TestCase): {'name': 'grault'}, {'id': ids[5], 'name': 'garply'} ]) + + def test_LINK_TO_pairs(self): + "LINK_TO commands can be written as pairs, instead of triplets" + ids = [ + self.address.create(self.cr, UID, {'name': 'foo'}), + self.address.create(self.cr, UID, {'name': 'bar'}), + self.address.create(self.cr, UID, {'name': 'baz'}) + ] + commands = map(lambda id: (4, id), ids) + + results = list(self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', commands, ['name'])) + + self.assertEqual(results, [ + {'id': ids[0], 'name': 'foo'}, + {'id': ids[1], 'name': 'bar'}, + {'id': ids[2], 'name': 'baz'} + ]) From 8734ffb6c2cb33106c1ebc423191b8c0adc6a24d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 11 Oct 2011 10:38:24 +0200 Subject: [PATCH 10/36] [IMP] raise assertion error if an o2m command is not one of CREATE, UPDATE or LINK_TO bzr revid: xmo@openerp.com-20111011083824-4g0ros6f3xgslkq4 --- openerp/osv/orm.py | 3 +++ tests/test_orm.py | 24 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 7cd2cbc22c5..8a447a838d5 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4796,6 +4796,9 @@ class BaseModel(object): else: commands.append(o2m_command) + assert not any(command for command, _, _ in commands if command not in (0, 1, 4)),\ + "Only CREATE, UPDATE and LINK_TO commands are supported in resolver" + # extract records to read, by id, in a mapping dict ids_to_read = [id for (command, id, _) in commands if command in (1, 4)] records_by_id = dict( diff --git a/tests/test_orm.py b/tests/test_orm.py index ff82b779cd9..1954e485e69 100644 --- a/tests/test_orm.py +++ b/tests/test_orm.py @@ -7,8 +7,11 @@ DB = os.environ['OPENERP_DATABASE'] CREATE = lambda values: (0, False, values) UPDATE = lambda id, values: (1, id, values) +DELETE = lambda id: (2, id, False) +FORGET = lambda id: (3, id, False) LINK_TO = lambda id: (4, id, False) - +DELETE_ALL = lambda: (5, False, False) +REPLACE_WITH = lambda ids: (6, False, ids) def setUpModule(): openerp.tools.config['addons_path'] = os.environ['OPENERP_ADDONS_PATH'] @@ -137,3 +140,22 @@ class TestO2MSerialization(unittest2.TestCase): {'id': ids[1], 'name': 'bar'}, {'id': ids[2], 'name': 'baz'} ]) + + def test_invalid_commands(self): + "Commands with uncertain semantics in this context should be forbidden" + + with self.assertRaises(AssertionError): + list(self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [DELETE(42)], ['name'])) + + with self.assertRaises(AssertionError): + list(self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [FORGET(42)], ['name'])) + + with self.assertRaises(AssertionError): + list(self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [DELETE_ALL()], ['name'])) + + with self.assertRaises(AssertionError): + list(self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [REPLACE_WITH([42])], ['name'])) From 2646a34cf08dc1c0f4d993d73f50824aee16d053 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 11 Oct 2011 10:48:52 +0200 Subject: [PATCH 11/36] [ADD] don't blow up on a singleton DELETE_ALL command (that command takes no parameters, so can have just (5,)) bzr revid: xmo@openerp.com-20111011084852-hborz317ru5pbzux --- openerp/osv/orm.py | 3 +++ tests/test_orm.py | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 8a447a838d5..39d05eab839 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4790,6 +4790,9 @@ class BaseModel(object): for o2m_command in o2m_commands: if not isinstance(o2m_command, (list, tuple)): commands.append((4, o2m_command, False)) + elif len(o2m_command) == 1: + (command,) = o2m_command + commands.append((command, False, False)) elif len(o2m_command) == 2: command, id = o2m_command commands.append((command, id, False)) diff --git a/tests/test_orm.py b/tests/test_orm.py index 1954e485e69..6e82bb9996b 100644 --- a/tests/test_orm.py +++ b/tests/test_orm.py @@ -141,6 +141,16 @@ class TestO2MSerialization(unittest2.TestCase): {'id': ids[2], 'name': 'baz'} ]) + def test_singleton_commands(self): + "DELETE_ALL can appear as a singleton" + + try: + list(self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [(5,)], ['name'])) + except AssertionError: + # 5 should fail with an assert error, but not e.g. a ValueError + pass + def test_invalid_commands(self): "Commands with uncertain semantics in this context should be forbidden" @@ -159,3 +169,4 @@ class TestO2MSerialization(unittest2.TestCase): with self.assertRaises(AssertionError): list(self.partner.resolve_o2m_commands_to_record_dicts( self.cr, UID, 'address', [REPLACE_WITH([42])], ['name'])) + From 8507ceddcf221b65b6214c24174737383f687452 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 11 Oct 2011 10:57:15 +0200 Subject: [PATCH 12/36] [IMP] make resolve_o2m_commands_to_record_dicts's result eager as per vmt's suggestion bzr revid: xmo@openerp.com-20111011085715-cvcficcy4di9q29i --- openerp/osv/orm.py | 4 +++- tests/test_orm.py | 45 ++++++++++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 39d05eab839..1d5a65456b6 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4809,12 +4809,14 @@ class BaseModel(object): for record in self.pool.get(o2m_model).read( cr, uid, ids_to_read, fields=fields, context=context)) + record_dicts = [] # merge record from db with record provided by command for command, id, record in commands: item = {} if command in (1, 4): item.update(records_by_id[id]) if command in (0, 1): item.update(record) - yield item + record_dicts.append(item) + return record_dicts # keep this import here, at top it will cause dependency cycle errors import expression diff --git a/tests/test_orm.py b/tests/test_orm.py index 6e82bb9996b..61b574df5eb 100644 --- a/tests/test_orm.py +++ b/tests/test_orm.py @@ -27,15 +27,16 @@ class TestO2MSerialization(unittest2.TestCase): def test_no_command(self): " empty list of commands yields an empty list of records " - results = list(self.partner.resolve_o2m_commands_to_record_dicts(self.cr, UID, 'address', [])) + results = self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', []) self.assertEqual(results, []) def test_CREATE_commands(self): " returns the VALUES dict as-is " - results = list(self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_o2m_commands_to_record_dicts( self.cr, UID, 'address', - map(CREATE, [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}]))) + map(CREATE, [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}])) self.assertEqual(results, [ {'foo': 'bar'}, {'foo': 'baz'}, @@ -51,7 +52,8 @@ class TestO2MSerialization(unittest2.TestCase): ] commands = map(LINK_TO, ids) - results = list(self.partner.resolve_o2m_commands_to_record_dicts(self.cr, UID, 'address', commands, ['name'])) + results = self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', commands, ['name']) self.assertEqual(results, [ {'id': ids[0], 'name': 'foo'}, @@ -67,7 +69,8 @@ class TestO2MSerialization(unittest2.TestCase): self.address.create(self.cr, UID, {'name': 'baz'}) ] - results = list(self.partner.resolve_o2m_commands_to_record_dicts(self.cr, UID, 'address', ids, ['name'])) + results = self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', ids, ['name']) self.assertEqual(results, [ {'id': ids[0], 'name': 'foo'}, @@ -81,12 +84,12 @@ class TestO2MSerialization(unittest2.TestCase): id_bar = self.address.create(self.cr, UID, {'name': 'bar'}) id_baz = self.address.create(self.cr, UID, {'name': 'baz', 'city': 'tag'}) - results = list(self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_o2m_commands_to_record_dicts( self.cr, UID, 'address', [ LINK_TO(id_foo), UPDATE(id_bar, {'name': 'qux', 'city': 'tagtag'}), UPDATE(id_baz, {'name': 'quux'}) - ], ['name', 'city'])) + ], ['name', 'city']) self.assertEqual(results, [ {'id': id_foo, 'name': 'foo', 'city': False}, @@ -100,7 +103,7 @@ class TestO2MSerialization(unittest2.TestCase): for name in ['NObar', 'baz', 'qux', 'NOquux', 'NOcorge', 'garply'] ] - results = list(self.partner.resolve_o2m_commands_to_record_dicts( + results = self.partner.resolve_o2m_commands_to_record_dicts( self.cr, UID, 'address', [ CREATE({'name': 'foo'}), UPDATE(ids[0], {'name': 'bar'}), @@ -110,7 +113,7 @@ class TestO2MSerialization(unittest2.TestCase): UPDATE(ids[4], {'name': 'corge'}), CREATE({'name': 'grault'}), LINK_TO(ids[5]) - ], ['name'])) + ], ['name']) self.assertEqual(results, [ {'name': 'foo'}, @@ -132,8 +135,8 @@ class TestO2MSerialization(unittest2.TestCase): ] commands = map(lambda id: (4, id), ids) - results = list(self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', commands, ['name'])) + results = self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', commands, ['name']) self.assertEqual(results, [ {'id': ids[0], 'name': 'foo'}, @@ -145,8 +148,8 @@ class TestO2MSerialization(unittest2.TestCase): "DELETE_ALL can appear as a singleton" try: - list(self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [(5,)], ['name'])) + self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [(5,)], ['name']) except AssertionError: # 5 should fail with an assert error, but not e.g. a ValueError pass @@ -155,18 +158,18 @@ class TestO2MSerialization(unittest2.TestCase): "Commands with uncertain semantics in this context should be forbidden" with self.assertRaises(AssertionError): - list(self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [DELETE(42)], ['name'])) + self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [DELETE(42)], ['name']) with self.assertRaises(AssertionError): - list(self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [FORGET(42)], ['name'])) + self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [FORGET(42)], ['name']) with self.assertRaises(AssertionError): - list(self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [DELETE_ALL()], ['name'])) + self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [DELETE_ALL()], ['name']) with self.assertRaises(AssertionError): - list(self.partner.resolve_o2m_commands_to_record_dicts( - self.cr, UID, 'address', [REPLACE_WITH([42])], ['name'])) + self.partner.resolve_o2m_commands_to_record_dicts( + self.cr, UID, 'address', [REPLACE_WITH([42])], ['name']) From e625e7672e438e658f4e028e49a798445b0e146d Mon Sep 17 00:00:00 2001 From: "Yogesh (OpenERP)" Date: Tue, 11 Oct 2011 14:55:20 +0530 Subject: [PATCH 13/36] [FIX] Configuration wizard display only type is manual and once. bzr revid: ysa@tinyerp.com-20111011092520-wzptl5t2titcp052 --- addons/web_dashboard/static/src/js/dashboard.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/web_dashboard/static/src/js/dashboard.js b/addons/web_dashboard/static/src/js/dashboard.js index 808fe64e5c7..66d7b69c891 100644 --- a/addons/web_dashboard/static/src/js/dashboard.js +++ b/addons/web_dashboard/static/src/js/dashboard.js @@ -292,6 +292,7 @@ openerp.web_dashboard.ConfigOverview = openerp.web.View.extend({ this._super(parent); this.dataset = new openerp.web.DataSetSearch( this, 'ir.actions.todo'); + this.dataset.domain = [['type', '!=', 'automatic']]; }, start: function () { this._super(); From 34fcc08a1f0722cd8eafd0dd2d8dd4d175f0e843 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 11 Oct 2011 11:45:51 +0200 Subject: [PATCH 14/36] [IMP] docstring for resolve_o2m_commands_... bzr revid: xmo@openerp.com-20111011094551-vr27on7iibdd9tv6 --- openerp/osv/orm.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 1d5a65456b6..3d9f8324abe 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4765,11 +4765,15 @@ class BaseModel(object): def resolve_o2m_commands_to_record_dicts(self, cr, uid, field_name, o2m_commands, fields=None, context=None): """ Serializes o2m commands into record dictionaries (as if all the o2m records came from the database via a read()), and - returns an iterator over these dictionaries. + returns an iterable over these dictionaries. Because o2m commands might be creation commands, not all record ids will contain an ``id`` field. Commands matching an - existing record (UPDATE and LINK_TO) will have an id. + existing record (``UPDATE`` and ``LINK_TO``) will have an id. + + .. note:: ``CREATE``, ``UPDATE`` and ``LINK_TO`` stand for the + o2m command codes ``0``, ``1`` and ``4`` + respectively :param field_name: name of the o2m field matching the commands :type field_name: str @@ -4778,10 +4782,11 @@ class BaseModel(object): :param fields: list of fields to read from the database, when applicable :type fields: list(str) :param context: request context + :raises AssertionError: if a command is not ``CREATE``, ``UPDATE`` or ``LINK_TO`` :returns: o2m records in a shape similar to that returned by ``read()`` (except records may be missing the ``id`` field if they don't exist in db) - :rtype: iter(dict) + :rtype: ``list(dict)`` """ o2m_model = self._all_columns[field_name].column._obj From 9b3903be983577f1aa96574e912f2b8cf51ae350 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Tue, 11 Oct 2011 12:54:26 +0200 Subject: [PATCH 15/36] [FIX] m2o of reference fields does not seem working m2o needs additional properties from view. reference template was broken now hide the m2o widget when no value is selected lp bug: https://launchpad.net/bugs/871677 fixed bzr revid: fme@openerp.com-20111011105426-2s4d82185q5ok4ry --- addons/web/static/src/js/view_form.js | 7 ++++++- addons/web/static/src/xml/base.xml | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index d583088f4da..ad94286e102 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2490,6 +2490,9 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({ }; this.get_fields_values = view.get_fields_values; this.do_onchange = this.on_form_changed = this.on_nop; + this.dataset = this.view.dataset; + this.widgets_counter = 0; + this.view_id = 'reference_' + _.uniqueId(); this.widgets = {}; this.fields = {}; this.selection = new openerp.web.form.FieldSelection(this, { attrs: { @@ -2505,8 +2508,10 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({ on_nop: function() { }, on_selection_changed: function() { - this.m2o.field.relation = this.selection.get_value(); + var sel = this.selection.get_value(); + this.m2o.field.relation = sel; this.m2o.set_value(null); + this.m2o.$element.toggle(sel !== false); }, start: function() { this._super(); diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 54bec76c4cf..d62382b57fe 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -878,7 +878,7 @@ - + From dd4e218028197ced2d95d1156683ee9b0fc6c700 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Tue, 11 Oct 2011 13:18:52 +0200 Subject: [PATCH 16/36] [FIX] Fix form frame float in firefox lp bug: https://launchpad.net/bugs/865953 fixed bzr revid: fme@openerp.com-20111011111852-6he82k62bo90ni9c --- addons/web/static/src/css/base.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 7bcf65ec3da..2734eecda98 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -828,6 +828,9 @@ label.error { } /* Form */ +.openerp .oe_frame.oe_forms { + clear: both; +} .openerp table.oe_frame td { color: #4c4c4c; } From d3f57c94b50b18094aab42e72a1c207ab6e29806 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Tue, 11 Oct 2011 13:29:11 +0200 Subject: [PATCH 17/36] [FIX] Fixed dashboard css global selector bzr revid: fme@openerp.com-20111011112911-adsysz0m5nazh5yr --- addons/web_dashboard/static/src/css/dashboard.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web_dashboard/static/src/css/dashboard.css b/addons/web_dashboard/static/src/css/dashboard.css index 720703879fe..e3178c58a4a 100644 --- a/addons/web_dashboard/static/src/css/dashboard.css +++ b/addons/web_dashboard/static/src/css/dashboard.css @@ -75,7 +75,7 @@ } /* Base overwriting */ -.openerp .oe-listview-content, .openerp .ui-widget-header { +.openerp .oe-dashboard .oe-listview-content, .openerp .oe-dashboard .ui-widget-header { border:none !important; padding:0px 3px; } From 039e42110f6abeeb92bdd522d0c029fa5a88a8af Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 11 Oct 2011 13:29:24 +0200 Subject: [PATCH 18/36] [IMP] copy colorization code from listview to treeview bzr revid: xmo@openerp.com-20111011112924-ih7on411mirf5wmi --- addons/web/static/src/js/view_tree.js | 42 ++++++++++++++++++++++++++- addons/web/static/src/xml/base.xml | 2 +- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/view_tree.js b/addons/web/static/src/js/view_tree.js index 7988d65805c..78818c069c0 100644 --- a/addons/web/static/src/js/view_tree.js +++ b/addons/web/static/src/js/view_tree.js @@ -32,6 +32,8 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# this.records = {}; this.options = _.extend({}, this.defaults, options || {}); + + _.bindAll(this, 'color_for'); }, start: function () { @@ -107,6 +109,43 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# $select.change(); } }); + + if (this.fields_view.arch.attrs.colors) { + return; + } + this.colors = _(this.fields_view.arch.attrs.colors.split(';')).chain() + .compact() + .map(function(color_pair) { + var pair = color_pair.split(':'), + color = pair[0], + expr = pair[1]; + return [color, py.parse(py.tokenize(expr)), expr]; + }).value(); + }, + /** + * Returns the color for the provided record in the current view (from the + * ``@colors`` attribute) + * + * @param {Object} record record for the current row + * @returns {String} CSS color declaration + */ + color_for: function (record) { + if (!this.colors) { return ''; } + var context = _.extend({}, record, { + uid: this.session.uid, + current_date: new Date().toString('yyyy-MM-dd') + // TODO: time, datetime, relativedelta + }); + for(var i=0, len=this.colors.length; i From a1eadf8a600bb8bcf1facee1a03758f07b2db01b Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Tue, 11 Oct 2011 13:54:24 +0200 Subject: [PATCH 19/36] [IMP] Form frame : Force nolabel='1' when widget='statusbar' Hardcoded exception but this way, we retain backward compatibility between addons & old clients bzr revid: fme@openerp.com-20111011115424-rme5btg594euln2v --- addons/web/static/src/js/view_form.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index ad94286e102..451360d3d0c 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -756,6 +756,10 @@ openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({ var type = {}; if (node.tag == 'field') { type = this.view.fields_view.fields[node.attrs.name] || {}; + if (node.attrs.widget == 'statusbar') { + // This way we can retain backward compatibility between addons and old clients + node.attrs.nolabel = '1'; + } } var widget = new (this.view.registry.get_any( [node.attrs.widget, type.type, node.tag])) (this.view, node); From 4c1c848be3ad0e9267ccef416a264f96a947d02f Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Tue, 11 Oct 2011 15:32:01 +0200 Subject: [PATCH 20/36] [FIX] Fixed list view's progress bar floating on the next table cell under firefox bzr revid: fme@openerp.com-20111011133201-wryn9twnf610h4fq --- addons/web/static/src/css/base.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 2734eecda98..26aa2adbf1f 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -721,6 +721,9 @@ label.error { .openerp .oe-listview .oe-field-cell { cursor: pointer; } +.openerp .oe-listview .oe-field-cell progress { + width: 100%; +} .openerp .oe-listview .oe-field-cell button { padding: 0; border: none; @@ -767,6 +770,10 @@ label.error { .openerp .oe-listview .oe-list-footer span { margin: 0 1em; } +.openerp .oe-listview .oe-list-footer progress { + vertical-align:-10% !important; + width: 100%; +} /** list rounded corners From 027b46bcac1be93f0b1369030c355e6e9c77a594 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Tue, 11 Oct 2011 15:37:13 +0200 Subject: [PATCH 21/36] [FIX] Fixed buttons overlapping when shrinking firefox windows bzr revid: fme@openerp.com-20111011133713-6eardsp1qv8subsg --- addons/web/static/src/css/base.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 26aa2adbf1f..6968cd93493 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -725,10 +725,11 @@ label.error { width: 100%; } .openerp .oe-listview .oe-field-cell button { + margin: 0; padding: 0; border: none; background: none; - width: 100%; + width: 16px; } .openerp .oe-listview .oe-field-cell button:active { opacity: 0.5; From ce1f472d57c9259021368c5d9662433c1bd47569 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Tue, 11 Oct 2011 15:44:17 +0200 Subject: [PATCH 22/36] [FIX] Fixed float menus toggler under firefox bzr revid: fme@openerp.com-20111011134417-4vsfwtkq2g10b2j7 --- addons/web/static/src/css/base.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 6968cd93493..6e38d16d26e 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -300,7 +300,7 @@ label.error { text-shadow: 0 1px 0 #333; text-align: center; font-size: 18px; - line-height: 14px; + line-height: 18px; right: 0; } .openerp .secondary_menu.oe_folded .oe_toggle_secondary_menu { @@ -325,6 +325,7 @@ label.error { background: #5A5858; vertical-align: top; height: 100%; + display: block; position: relative; } .openerp .secondary_menu.oe_folded { From c428a8057003c1370e89274a96935a7dbed935e6 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Tue, 11 Oct 2011 15:52:53 +0200 Subject: [PATCH 23/36] [fix] problem in crm module extension to res.partner view, trying to set the value of a many2one from a corresponding o2m, which is useless and makes the web client crash bzr revid: nicolas.vanhoren@openerp.com-20111011135253-vne15mxazqbl02jc --- addons/crm/res_partner_view.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/crm/res_partner_view.xml b/addons/crm/res_partner_view.xml index 8f9012b1bf0..79b8bebb55d 100644 --- a/addons/crm/res_partner_view.xml +++ b/addons/crm/res_partner_view.xml @@ -53,7 +53,7 @@ - + @@ -84,7 +84,7 @@ icon="gtk-go-up" /> - + @@ -92,7 +92,7 @@ - + From 6bfd0ec586d22f98a3f61fe56aa219fc0e924981 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 11 Oct 2011 16:19:21 +0200 Subject: [PATCH 24/36] [IMP] sale: replaced hardcoded date/time formats by appropriate constants bzr revid: odo@openerp.com-20111011141921-chzenvq0i2kvbnw7 --- addons/sale/sale.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 4fd63af4fd4..d0a3ccbf050 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -25,6 +25,7 @@ import time import pooler from osv import fields, osv from tools.translate import _ +from tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT import decimal_precision as dp import netsvc @@ -268,7 +269,7 @@ class sale_order(osv.osv): } _defaults = { 'picking_policy': 'direct', - 'date_order': lambda *a: time.strftime('%Y-%m-%d'), + 'date_order': lambda *a: time.strftime(DEFAULT_SERVER_DATE_FORMAT), 'order_policy': 'manual', 'state': 'draft', 'user_id': lambda obj, cr, uid, context: uid, @@ -435,7 +436,7 @@ class sale_order(osv.osv): } inv.update(self._inv_get(cr, uid, order)) inv_id = inv_obj.create(cr, uid, inv, context=context) - data = inv_obj.onchange_payment_term_date_invoice(cr, uid, [inv_id], pay_term, time.strftime('%Y-%m-%d')) + data = inv_obj.onchange_payment_term_date_invoice(cr, uid, [inv_id], pay_term, time.strftime(DEFAULT_SERVER_DATE_FORMAT)) if data.get('value', False): inv_obj.write(cr, uid, [inv_id], data['value'], context=context) inv_obj.button_compute(cr, uid, [inv_id]) @@ -619,9 +620,9 @@ class sale_order(osv.osv): def action_wait(self, cr, uid, ids, *args): for o in self.browse(cr, uid, ids): if (o.order_policy == 'manual'): - self.write(cr, uid, [o.id], {'state': 'manual', 'date_confirm': time.strftime('%Y-%m-%d')}) + self.write(cr, uid, [o.id], {'state': 'manual', 'date_confirm': time.strftime(DEFAULT_SERVER_DATE_FORMAT)}) else: - self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': time.strftime('%Y-%m-%d')}) + self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': time.strftime(DEFAULT_SERVER_DATE_FORMAT)}) self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line]) message = _("The quotation '%s' has been converted to a sales order.") % (o.name,) self.log(cr, uid, o.id, message) @@ -759,8 +760,8 @@ class sale_order(osv.osv): if line.state == 'done': continue - date_planned = datetime.strptime(order.date_order, '%Y-%m-%d') + relativedelta(days=line.delay or 0.0) - date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime('%Y-%m-%d %H:%M:%S') + date_planned = datetime.strptime(order.date_order, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=line.delay or 0.0) + date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT) if line.product_id: if line.product_id.product_tmpl_id.type in ('product', 'consu'): @@ -845,7 +846,7 @@ class sale_order(osv.osv): 'description': 'Order '+str(inv['id']), 'document': '', 'partner_id': part, - 'date': time.strftime('%Y-%m-%d'), + 'date': time.strftime(DEFAULT_SERVER_DATE_FORMAT), 'user_id': uid, 'partner_type': partnertype, 'probability': 1.0, @@ -1094,7 +1095,7 @@ class sale_order_line(osv.osv): 'product_uos_qty': qty}, 'domain': {'product_uom': [], 'product_uos': []}} if not date_order: - date_order = time.strftime('%Y-%m-%d') + date_order = time.strftime(DEFAULT_SERVER_DATE_FORMAT) result = {} product_obj = product_obj.browse(cr, uid, product, context=context) From c16befa98321a5b5212ee74ab85dddf35b1782fc Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 11 Oct 2011 16:41:46 +0200 Subject: [PATCH 25/36] [IMP] orm: resolve_o2m_commands_...: simplify assert, remove context doc following guidelines bzr revid: odo@openerp.com-20111011144146-b9rt5kc9sb5lj0dr --- openerp/osv/orm.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 3d9f8324abe..ce51ccc1d44 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4781,7 +4781,6 @@ class BaseModel(object): :type o2m_commands: list((int|False, int|False, dict|False)) :param fields: list of fields to read from the database, when applicable :type fields: list(str) - :param context: request context :raises AssertionError: if a command is not ``CREATE``, ``UPDATE`` or ``LINK_TO`` :returns: o2m records in a shape similar to that returned by ``read()`` (except records may be missing the ``id`` @@ -4794,7 +4793,8 @@ class BaseModel(object): commands = [] for o2m_command in o2m_commands: if not isinstance(o2m_command, (list, tuple)): - commands.append((4, o2m_command, False)) + command = 4 + commands.append((command, o2m_command, False)) elif len(o2m_command) == 1: (command,) = o2m_command commands.append((command, False, False)) @@ -4802,10 +4802,10 @@ class BaseModel(object): command, id = o2m_command commands.append((command, id, False)) else: + command = o2m_command[0] commands.append(o2m_command) - - assert not any(command for command, _, _ in commands if command not in (0, 1, 4)),\ - "Only CREATE, UPDATE and LINK_TO commands are supported in resolver" + assert command in (0, 1, 4), \ + "Only CREATE, UPDATE and LINK_TO commands are supported in resolver" # extract records to read, by id, in a mapping dict ids_to_read = [id for (command, id, _) in commands if command in (1, 4)] @@ -4843,7 +4843,7 @@ class Model(BaseModel): class TransientModel(BaseModel): """Model super-class for transient records, meant to be temporarily persisted, and regularly vaccuum-cleaned. - + A TransientModel has a simplified access rights management, all users can create new records, and may only access the records they created. The super-user has unrestricted access From b819542c8f574750a3fe3b8c563eee20d38846bf Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Tue, 11 Oct 2011 17:35:35 +0200 Subject: [PATCH 26/36] [fix] problem with form view, does not prevent multiple creates/saves bzr revid: nicolas.vanhoren@openerp.com-20111011153535-ojo8qqpzlg3ap9fy --- addons/web/static/src/js/data.js | 4 ++ addons/web/static/src/js/view_form.js | 68 ++++++++++++++------------- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index 76e430dc34a..ea0824fb1df 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -639,6 +639,10 @@ openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({ self.to_write.push(record); } var cached = _.detect(this.cache, function(x) {return x.id === id;}); + if (!cached) { + cached = {id: id, values: {}}; + this.cache.push(cached); + } $.extend(cached.values, record.values); if (dirty) this.on_change(); diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index c2b16ff22fc..c940f4909e9 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -44,6 +44,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# this.translatable_fields = []; _.defaults(this.options, {"always_show_new_button": true, "not_interactible_on_create": false}); + this.save_lock = $.Deferred().resolve(); }, start: function() { this._super(); @@ -365,42 +366,45 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# */ do_save: function(success, prepend_on_create) { var self = this; - if (!this.ready) { - return $.Deferred().reject(); - } - var form_dirty = false, - form_invalid = false, - values = {}, - first_invalid_field = null; - for (var f in this.fields) { - f = this.fields[f]; - if (!f.is_valid()) { - form_invalid = true; - f.update_dom(); - if (!first_invalid_field) { - first_invalid_field = f; + var action = function() { + if (!self.ready) { + return $.Deferred().reject(); + } + var form_dirty = false, + form_invalid = false, + values = {}, + first_invalid_field = null; + for (var f in self.fields) { + f = self.fields[f]; + if (!f.is_valid()) { + form_invalid = true; + f.update_dom(); + if (!first_invalid_field) { + first_invalid_field = f; + } + } else if (f.is_dirty()) { + form_dirty = true; + values[f.name] = f.get_value(); } - } else if (f.is_dirty()) { - form_dirty = true; - values[f.name] = f.get_value(); } - } - if (form_invalid) { - first_invalid_field.focus(); - this.on_invalid(); - return $.Deferred().reject(); - } else { - console.log("About to save", values); - if (!this.datarecord.id) { - return this.dataset.create(values).pipe(function(r) { - return self.on_created(r, undefined, prepend_on_create); - }).then(success); + if (form_invalid) { + first_invalid_field.focus(); + self.on_invalid(); + return $.Deferred().reject(); } else { - return this.dataset.write(this.datarecord.id, values, {}).pipe(function(r) { - return self.on_saved(r); - }).then(success); + console.log("About to save", values); + if (!self.datarecord.id) { + return self.dataset.create(values).pipe(function(r) { + return self.on_created(r, undefined, prepend_on_create); + }).then(success); + } else { + return self.dataset.write(self.datarecord.id, values, {}).pipe(function(r) { + return self.on_saved(r); + }).then(success); + } } - } + }; + this.save_lock = this.save_lock.pipe(action, action); }, do_save_edit: function() { this.do_save(); From 9872fdd9116f13662d8423550473ce788d80c8ac Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Tue, 11 Oct 2011 17:47:00 +0200 Subject: [PATCH 27/36] [FIX] webrpc double load jquery bzr revid: al@openerp.com-20111011154700-2mnim3luoersk1c3 --- addons/web_rpc/__openerp__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/web_rpc/__openerp__.py b/addons/web_rpc/__openerp__.py index c725489497c..c1c4bfabe80 100644 --- a/addons/web_rpc/__openerp__.py +++ b/addons/web_rpc/__openerp__.py @@ -3,6 +3,7 @@ "category" : "Hidden", "version" : "2.0", "depends" : [], + "installable" : False, 'active': False, 'js' : [ "../web/static/lib/datejs/date-en-US.js", From b49b1cfb104e85922666ccd567a4fa8d131a83be Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Tue, 11 Oct 2011 17:59:50 +0200 Subject: [PATCH 28/36] [IMP] Dashboard: use parent's do_action bzr revid: fme@openerp.com-20111011155950-z620hv2zxtccm501 --- .../web_dashboard/static/src/js/dashboard.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/addons/web_dashboard/static/src/js/dashboard.js b/addons/web_dashboard/static/src/js/dashboard.js index adcf91fd06c..24b3a2339fd 100644 --- a/addons/web_dashboard/static/src/js/dashboard.js +++ b/addons/web_dashboard/static/src/js/dashboard.js @@ -214,6 +214,8 @@ openerp.web.form.DashBoard = openerp.web.form.Widget.extend({ }); }, on_load_action: function(result) { + var self = this; + var action_orig = _.extend({}, result.result); var action = result.result; action.flags = { search_view : false, @@ -228,6 +230,14 @@ openerp.web.form.DashBoard = openerp.web.form.Widget.extend({ this.action_managers.push(am); am.appendTo($("#"+this.view.element_id + '_action_' + action.id)); am.do_action(action); + am.do_action = function(action) { + self.do_action(action); + } + if (am.inner_viewmanager) { + am.inner_viewmanager.on_mode_switch.add(function(mode) { + self.do_action(action_orig); + }); + } }, render: function() { // We should start with three columns available @@ -241,12 +251,10 @@ openerp.web.form.DashBoard = openerp.web.form.Widget.extend({ return QWeb.render(this.template, this); }, do_reload: function() { - _.each(this.action_managers, function(am) { - am.stop(); - }); - this.action_managers = []; + var view_manager = this.view.widget_parent, + action_manager = view_manager.widget_parent; this.view.stop(); - this.view.start(); + action_manager.do_action(view_manager.action); } }); openerp.web.form.DashBoardLegacy = openerp.web.form.DashBoard.extend({ From 102ab251049db7ac587ac55c8f7948a5caa950d8 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 11 Oct 2011 18:27:35 +0200 Subject: [PATCH 29/36] [REM] removed lp auto-committed PO junk bzr revid: odo@openerp.com-20111011162735-eor1vnl30dwbdi1h --- debian/po/sl.po | 41 ----------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 debian/po/sl.po diff --git a/debian/po/sl.po b/debian/po/sl.po deleted file mode 100644 index b62aaea9555..00000000000 --- a/debian/po/sl.po +++ /dev/null @@ -1,41 +0,0 @@ -# Slovenian translation for openobject-server -# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 -# This file is distributed under the same license as the openobject-server package. -# FIRST AUTHOR , 2011. -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-server\n" -"Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2009-08-24 22:41+0300\n" -"PO-Revision-Date: 2011-10-10 06:08+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Slovenian \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-10-11 05:29+0000\n" -"X-Generator: Launchpad (build 14123)\n" - -#. Type: string -#. Description -#: ../openerp-server.templates:1001 -msgid "Dedicated system account for the Open ERP server:" -msgstr "Namenski sistemski račun za ta OpenERP strežnik:" - -#. Type: string -#. Description -#: ../openerp-server.templates:1001 -msgid "" -"The Open ERP server must use a dedicated account for its operation so that " -"the system's security is not compromised by running it with superuser " -"privileges." -msgstr "" -"OpenERP strežnik mora uporabljati namenski račun za njegovo delovanje, da " -"varnost strežnika ni ogrožena z zaganjanjem v privelegijih superuser." - -#. Type: string -#. Description -#: ../openerp-server.templates:1001 -msgid "Please choose that account's username." -msgstr "Prosim, izberite uporabniško ime računa." From 4140e3546a9741d225369beddaa36beb76cd8704 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 11 Oct 2011 18:34:35 +0200 Subject: [PATCH 30/36] [FIX] expression: proper handling of empty string in m2o domain criterions Also fixed incorrect name_search() implementations in res.partner and res.currency, and added corresponding tests. lp bug: https://launchpad.net/bugs/861350 fixed bzr revid: odo@openerp.com-20111011163435-szj9vm6x9x8nryn8 --- openerp/addons/base/res/res_currency.py | 14 +++---- openerp/addons/base/res/res_partner.py | 13 +++---- .../addons/base/test/test_osv_expression.yml | 38 ++++++++++++++----- openerp/osv/expression.py | 24 ++++-------- openerp/osv/orm.py | 3 +- 5 files changed, 50 insertions(+), 42 deletions(-) diff --git a/openerp/addons/base/res/res_currency.py b/openerp/addons/base/res/res_currency.py index 4e7029d5172..32aa387e828 100644 --- a/openerp/addons/base/res/res_currency.py +++ b/openerp/addons/base/res/res_currency.py @@ -109,14 +109,14 @@ class res_currency(osv.osv): def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100): if not args: args = [] - if name: - ids = self.search(cr, user, ([('name','=',name)] + args), limit=limit, context=context) + results = super(res_currency,self)\ + .name_search(cr, user, name, args, operator=operator, context=context, limit=limit) + if not results: name_match = CURRENCY_DISPLAY_PATTERN.match(name) - if not ids and name_match: - ids = self.search(cr, user, [('name','=', name_match.group(1))] + args, limit=limit, context=context) - else: - ids = self.search(cr, user, args, limit=limit, context=context) - return self.name_get(cr, user, ids, context=context) + if name_match: + results = super(res_currency,self)\ + .name_search(cr, user, name_match.group(1), args, operator=operator, context=context, limit=limit) + return results def name_get(self, cr, uid, ids, context=None): if not ids: diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index ae58c9a9f33..b250269cb0e 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -195,14 +195,13 @@ class res_partner(osv.osv): def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100): if not args: - args=[] - if name: + args = [] + # short-circuit ref match when possible + if name and operator in ('=', 'ilike', '=ilike', 'like'): ids = self.search(cr, uid, [('ref', '=', name)] + args, limit=limit, context=context) - if not ids: - ids = self.search(cr, uid, [('name', operator, name)] + args, limit=limit, context=context) - else: - ids = self.search(cr, uid, args, limit=limit, context=context) - return self.name_get(cr, uid, ids, context) + if ids: + return self.name_get(cr, uid, ids, context) + return super(res_partner,self).name_search(cr, uid, name, args, operator=operator, context=context, limit=limit) def _email_send(self, cr, uid, ids, email_from, subject, body, on_error=None): partners = self.browse(cr, uid, ids) diff --git a/openerp/addons/base/test/test_osv_expression.yml b/openerp/addons/base/test/test_osv_expression.yml index f67a99cdf46..d9a1b215000 100644 --- a/openerp/addons/base/test/test_osv_expression.yml +++ b/openerp/addons/base/test/test_osv_expression.yml @@ -235,11 +235,14 @@ res_3.sort() res_4 = self.search(cr, uid, [('parent_id', 'not in', [False])]) # get rows with not null parent_id res_4.sort() - assert res_0 == partner_ids - assert res_1 == partner_ids - assert res_2 == with_parent - assert res_3 == partner_ids - assert res_4 == with_parent + res_4b = self.search(cr, uid, [('parent_id', 'not ilike', '')]) # get only rows without parent + res_4b.sort() + assert res_0 == partner_ids, 'res0: expected %r, got %r' % (partner_ids, res_0) + assert res_1 == partner_ids, 'res1: expected %r, got %r' % (partner_ids, res_1) + assert res_2 == with_parent, 'res2: expected %r, got %r' % (with_parent, res_2) + assert res_3 == partner_ids, 'res3: expected %r, got %r' % (partner_ids, res_3) + assert res_4 == with_parent, 'res4: expected %r, got %r' % (with_parent, res_4) + assert res_4b == without_parent, 'res4b: expected %r, got %r' % (without_parent, res_4b) # The results of these queries, when combined with queries 0..4 must # give the whole set of ids. res_5 = self.search(cr, uid, [('parent_id', 'like', 'probably_unexisting_name')]) @@ -252,11 +255,15 @@ res_8.sort() res_9 = self.search(cr, uid, [('parent_id', 'in', [False])]) res_9.sort() - assert res_5 == [] - assert res_6 == [] - assert res_7 == without_parent - assert res_8 == [] - assert res_9 == without_parent + res_9b = self.search(cr, uid, [('parent_id', 'ilike', '')]) # get those with a parent + res_9b.sort() + + assert res_5 == [], 'res5: expected %r, got %r' % ([], res_5) + assert res_6 == [], 'res6: expected %r, got %r' % ([], res_6) + assert res_7 == without_parent, 'res7: expected %r, got %r' % (without_parent, res_7) + assert res_8 == [], 'res8: expected %r, got %r' % ([], res_8) + assert res_9 == without_parent, 'res9: expected %r, got %r' % (without_parent, res_9) + assert res_9b == with_parent, 'res9b: expected %r, got %r' % (with_parent, res_9b) # These queries must return exactly the results than the queries 0..4, # i.e. not ... in ... must be the same as ... not in ... . res_10 = self.search(cr, uid, ['!', ('parent_id', 'like', 'probably_unexisting_name')]) @@ -287,6 +294,17 @@ res_17 = self.search(cr, uid, [('website', 'not in', False)]) res_17.sort() assert res_17 == with_website + +- + Check behavior for required many2one fields +- + !python {model: res.company }: | + company_ids = sorted(self.search(cr, uid, [])) + # currency_id is required + res_101 = sorted(self.search(cr, uid, [('currency_id', 'not ilike', '')])) # get no companies + res_102 = sorted(self.search(cr, uid, [('currency_id', 'ilike', '')])) # get all companies + assert res_101 == [], 'res_101: expected %r, got %r' % ([], res_101) + assert res_102 == company_ids, 'res_102: expected %r, got %r' % (company_ids, res_102) - Property of the query (one2many not in False). - diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py index cf7ef3b70cf..2d96963f516 100644 --- a/openerp/osv/expression.py +++ b/openerp/osv/expression.py @@ -593,23 +593,13 @@ class expression(object): if operator in NEGATIVE_TERM_OPERATORS: res_ids.append(False) # TODO this should not be appended if False was in 'right' return (left, 'in', res_ids) - - m2o_str = False - if right: - if isinstance(right, basestring): # and not isinstance(field, fields.related): - m2o_str = True - elif isinstance(right, (list, tuple)): - m2o_str = True - for ele in right: - if not isinstance(ele, basestring): - m2o_str = False - break - if m2o_str: - self.__exp[i] = _get_expression(field_obj, cr, uid, left, right, operator, context=context) - elif right == []: - pass # Handled by __leaf_to_sql(). - else: # right is False - pass # Handled by __leaf_to_sql(). + # resolve string-based m2o criterion into IDs + if isinstance(right, basestring) or \ + right and isinstance(right, (tuple,list)) and all(isinstance(item, basestring) for item in right): + self.__exp[i] = _get_expression(field_obj, cr, uid, left, right, operator, context=context) + else: + # right == [] or right == False and all other cases are handled by __leaf_to_sql() + pass else: # other field type diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index ce51ccc1d44..2f924aceeb6 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2265,7 +2265,8 @@ class BaseModel(object): if context is None: context = {} args = args[:] - if name: + # optimize out the default criterion of ``ilike ''`` that matches everything + if not (name == '' and operator == 'ilike'): args += [(self._rec_name, operator, name)] access_rights_uid = name_get_uid or user ids = self._search(cr, user, args, limit=limit, context=context, access_rights_uid=access_rights_uid) From 76c702ca3cd271ec69a2be99660bdf6ff6a9a483 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 11 Oct 2011 19:11:48 +0200 Subject: [PATCH 31/36] [FIX] Model.import_data docstring: cleanup + clear RST warnings for autodoc The pipe character is taken as an inline substitution marker, and is easy to avoid in most cases except within literal blocks. bzr revid: odo@openerp.com-20111011171148-rluwokcsx7i38i6e --- openerp/osv/orm.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 2f924aceeb6..ad40ac66093 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1175,8 +1175,7 @@ class BaseModel(object): return {'datas': datas} def import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None): - """ - Import given data in given module + """Import given data in given module This method is used when importing data via client menu. @@ -1191,21 +1190,26 @@ class BaseModel(object): order_line/product_uom_qty, order_line/product_uom/id (=xml_id) - This method returns a 4-tuple with the following structure: + This method returns a 4-tuple with the following structure:: - * The first item is a return code, it returns either ``-1`` in case o + (return_code, errored_resource, error_message, unused) - :param cr: database cursor - :param uid: current user id - :param fields: list of fields + * The first item is a return code, it is ``-1`` in case of + import error, or the last imported row number in case of success + * The second item contains the record data dict that failed to import + in case of error, otherwise it's 0 + * The third item contains an error message string in case of error, + otherwise it's 0 + * The last item is currently unused, with no specific semantics + + :param fields: list of fields to import :param data: data to import :param mode: 'init' or 'update' for record creation :param current_module: module name :param noupdate: flag for record creation - :param context: context arguments, like lang, time zone, :param filename: optional file to store partial import state for recovery - :returns: 4-tuple of a return code, an errored resource, an error message and ??? - :rtype: (int, dict|0, str|0, ''|0) + :returns: 4-tuple in the form (return_code, errored_resource, error_message, unused) + :rtype: (int, dict or 0, str or 0, str or 0) """ if not context: context = {} From 1ce5334c819175bb8690fcb9a47b560fc7c7f9f4 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Tue, 11 Oct 2011 23:07:42 +0200 Subject: [PATCH 32/36] [IMP] openerplib exception handling step1 We define the 4 standard openerp exceptions introduced in the new xmlrpc/1 and jsonrpc/1 api. But we dont raise them yet, instead we update the local connector to emulate the current xmlrpc faults. bzr revid: al@openerp.com-20111011210742-i5zdn7kldg74kx2b --- addons/web/common/openerplib/main.py | 58 ++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/addons/web/common/openerplib/main.py b/addons/web/common/openerplib/main.py index b09e9f9d72f..217cb221cd9 100644 --- a/addons/web/common/openerplib/main.py +++ b/addons/web/common/openerplib/main.py @@ -55,6 +55,33 @@ _logger = logging.getLogger(__name__) def _getChildLogger(logger, subname): return logging.getLogger(logger.name + "." + subname) +#---------------------------------------------------------- +# Exceptions +# TODO openerplib should raise those instead of xmlrpc faults: +#---------------------------------------------------------- + +class LibException(Exception): + """ Base of all client lib exceptions """ + def __init__(self,code=None,message=None): + self.code = code + self.message = message + +class ApplicationError(LibException): + """ maps to code: 1, server side: Exception or openerp.exceptions.DeferredException""" + +class Warning(LibException): + """ maps to code: 2, server side: openerp.exceptions.Warning""" + +class AccessError(LibException): + """ maps to code: 3, server side: openerp.exceptions.AccessError""" + +class AccessDenied(LibException): + """ maps to code: 4, server side: openerp.exceptions.AccessDenied""" + +#---------------------------------------------------------- +# Connectors +#---------------------------------------------------------- + class Connector(object): """ The base abstract class representing a connection to an OpenERP Server. @@ -99,6 +126,7 @@ class XmlRPCConnector(Connector): def send(self, service_name, method, *args): url = '%s/%s' % (self.url, service_name) service = xmlrpclib.ServerProxy(url) + # TODO should try except and wrap exception into LibException return getattr(service, method)(*args) class NetRPC_Exception(Exception): @@ -212,18 +240,34 @@ class LocalConnector(Connector): def send(self, service_name, method, *args): import openerp - # TODO Exception handling - # This will be changed to be xmlrpc compatible - # OpenERPWarning code 1 - # OpenERPException code 2 + import traceback try: result = openerp.netsvc.dispatch_rpc(service_name, method, args) - except: - exc_type, exc_value, exc_tb = sys.exc_info() - fault = xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)) + except Exception,e: + # TODO change the except to raise LibException instead of their emulated xmlrpc fault + if isinstance(e, openerp.osv.osv.except_osv): + fault = xmlrpclib.Fault('warning -- ' + e.name + '\n\n' + e.value, '') + elif isinstance(e, openerp.exceptions.Warning): + fault = xmlrpclib.Fault('warning -- Warning\n\n' + str(e), '') + elif isinstance(e, openerp.exceptions.AccessError): + fault = xmlrpclib.Fault('warning -- AccessError\n\n' + str(e), '') + elif isinstance(e, openerp.exceptions.AccessDenied): + fault = xmlrpclib.Fault('AccessDenied', str(e)) + elif isinstance(e, openerp.exceptions.DeferredException): + info = e.traceback + formatted_info = "".join(traceback.format_exception(*info)) + fault = xmlrpclib.Fault(openerp.tools.ustr(e.message), formatted_info) + else: + info = sys.exc_info() + formatted_info = "".join(traceback.format_exception(*info)) + fault = xmlrpclib.Fault(openerp.tools.exception_to_unicode(e), formatted_info) raise fault return result +#---------------------------------------------------------- +# Public api +#---------------------------------------------------------- + class Service(object): """ A class to execute RPC calls on a specific service of the remote server. From b574f20012883e86af064be40e29a26013b1de7d Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Tue, 11 Oct 2011 23:20:17 +0200 Subject: [PATCH 33/36] [IMP] board module and hr dashboard cleanups split board.board object (almost unsed) xml's from actual board definitions board module defines 2 boards: the admin one, the homepage clean hr board definitions remove hr menuitems from administration menu remove useless menuitem definitions bzr revid: al@openerp.com-20111011212017-qd3d2xp9mfneagqq --- addons/board/__openerp__.py | 12 +- addons/board/board.py | 5 - ...stration_view.xml => board_data_admin.xml} | 29 +-- addons/board/board_data_home.xml | 68 ++++++ addons/board/board_demo.xml | 9 +- addons/board/board_view.xml | 196 ++++++------------ addons/board/security/board_security.xml | 10 - addons/board/security/ir.model.access.csv | 1 - addons/hr/__openerp__.py | 7 +- addons/hr/board_hr_manager_view.xml | 31 --- addons/hr/board_hr_view.xml | 40 ---- addons/hr/hr_board.xml | 62 ++++++ .../board_hr_expense_manager_view.xml | 7 - addons/hr_expense/board_hr_expense_view.xml | 7 - addons/hr_holidays/board_hr_holidays_view.xml | 11 - .../board_hr_manager_holidays_view.xml | 9 +- addons/hr_holidays/hr_holidays_wizard.xml | 6 - .../board_hr_recruitment_statistical_view.xml | 8 - addons/hr_timesheet/hr_timesheet_report.xml | 5 - .../hr_timesheet_invoice_report.xml | 5 - .../board_hr_timesheet_view.xml | 18 -- addons/point_of_sale/static/src/pos.html | 2 +- 22 files changed, 219 insertions(+), 329 deletions(-) rename addons/board/{board_administration_view.xml => board_data_admin.xml} (92%) create mode 100644 addons/board/board_data_home.xml delete mode 100644 addons/board/security/board_security.xml delete mode 100644 addons/hr/board_hr_manager_view.xml delete mode 100644 addons/hr/board_hr_view.xml create mode 100644 addons/hr/hr_board.xml diff --git a/addons/board/__openerp__.py b/addons/board/__openerp__.py index 7f37f125903..69515dc2840 100644 --- a/addons/board/__openerp__.py +++ b/addons/board/__openerp__.py @@ -34,8 +34,16 @@ The user can also publish notes. """, 'author': 'OpenERP SA', 'depends': ['base'], - 'update_xml': ['security/board_security.xml','security/ir.model.access.csv', 'wizard/board_menu_create_view.xml', 'board_view.xml','board_administration_view.xml'], - 'demo_xml': ['board_demo.xml'], + 'update_xml': [ + 'security/ir.model.access.csv', + 'wizard/board_menu_create_view.xml', + 'board_view.xml', + 'board_data_admin.xml', + 'board_data_home.xml', + ], + 'demo_xml': [ + 'board_demo.xml' + ], 'installable': True, 'active': False, 'certificate': '0076912305725', diff --git a/addons/board/board.py b/addons/board/board.py index d215981953a..6a87d5b190b 100644 --- a/addons/board/board.py +++ b/addons/board/board.py @@ -168,9 +168,6 @@ class board_board(osv.osv): 'name':lambda *args: 'Dashboard' } -board_board() - - class board_line(osv.osv): """ Board Line @@ -193,8 +190,6 @@ class board_line(osv.osv): 'position': lambda *args: 'left' } -board_line() - class res_log_report(osv.osv): """ Log Report """ _name = "res.log.report" diff --git a/addons/board/board_administration_view.xml b/addons/board/board_data_admin.xml similarity index 92% rename from addons/board/board_administration_view.xml rename to addons/board/board_data_admin.xml index 9e22dbd3fb6..1f45a6f5a72 100644 --- a/addons/board/board_administration_view.xml +++ b/addons/board/board_data_admin.xml @@ -1,7 +1,6 @@ - - + user.connection.tree @@ -158,23 +157,13 @@ - - - - + + - + + + + + + - - - - - - - - - - - - diff --git a/addons/board/board_data_home.xml b/addons/board/board_data_home.xml new file mode 100644 index 00000000000..ac141e1d2c7 --- /dev/null +++ b/addons/board/board_data_home.xml @@ -0,0 +1,68 @@ + + + + + + Applications Tiles + board.home.applications + + + Tweets Widget + board.home.widgets + + + + Events Widget + board.home.widgets + + + + Facebook Widget + board.home.widgets + + + + Note Widget + board.home.widgets + + + + Google Maps Widget + board.home.widgets + + + + Currency Converter Widget + board.home.widgets + + + + + + Homepage Board + board.board + form + +
+ + + + + + + + + +
+
+
+ + Home Page + board.board + form + form + + + +
+
diff --git a/addons/board/board_demo.xml b/addons/board/board_demo.xml index 2f704e640bc..d36ec6161fe 100644 --- a/addons/board/board_demo.xml +++ b/addons/board/board_demo.xml @@ -1,8 +1,5 @@ - - - - - - \ No newline at end of file + + + diff --git a/addons/board/board_view.xml b/addons/board/board_view.xml index b5048fa7b3d..36d75002476 100644 --- a/addons/board/board_view.xml +++ b/addons/board/board_view.xml @@ -1,146 +1,74 @@ - - - Applications Tiles - board.home.applications - - - Tweets Widget - board.home.widgets - - - - Events Widget - board.home.widgets - - - - Facebook Widget - board.home.widgets - - - - Note Widget - board.home.widgets - - - - Google Maps Widget - board.home.widgets - - - - Currency Converter Widget - board.home.widgets - - - - Homepage Board + + + + board.board.search + board.board + search + + + + + + + + + + + board.board.tree + board.board + tree + + + + + + + + + + board.board.form board.board form + -
- - - - - - - - - -
-
-
- - Home Page - board.board - form - form - - - - - - - board.board.tree - board.board - tree - - +
+ - - - - - - - - board.board.form - board.board - form - - - - + +