From 33eb3dffb2bd3ac9d886a17c3c67ea83e65dcaa1 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Mon, 7 Jul 2014 11:50:30 +0200 Subject: [PATCH 01/21] [REM] models: remove the magic methods signal_XXX() --- addons/account/account_invoice.py | 2 +- addons/mrp/mrp.py | 4 ++-- addons/mrp/procurement.py | 2 +- addons/mrp/stock.py | 4 ++-- addons/point_of_sale/point_of_sale.py | 10 +++++----- addons/purchase/purchase.py | 16 ++++++++-------- .../purchase_requisition/purchase_requisition.py | 8 ++++---- addons/sale/wizard/sale_make_invoice.py | 2 +- addons/sale_stock/sale_stock.py | 4 ++-- .../sale_stock/test/cancel_order_sale_stock.yml | 2 +- addons/sale_stock/test/picking_order_policy.yml | 2 +- addons/stock/wizard/make_procurement_product.py | 2 +- addons/website_quote/controllers/main.py | 2 +- openerp/addons/base/ir/ir_actions.py | 4 ++-- openerp/models.py | 13 ------------- 15 files changed, 32 insertions(+), 45 deletions(-) diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index ad99ae40058..455feb14afb 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -98,7 +98,7 @@ class account_invoice(models.Model): def _compute_reconciled(self): self.reconciled = self.test_paid() if not self.reconciled and self.state == 'paid': - self.signal_open_test() + self.signal_workflow('open_test') @api.model def _get_reference_type(self): diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index 73d0688a543..453bfe5c458 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -884,7 +884,7 @@ class mrp_production(osv.osv): production = self.browse(cr, uid, production_id, context=context) if not production.move_lines and production.state == 'ready': # trigger workflow if not products to consume (eg: services) - self.signal_button_produce(cr, uid, [production_id]) + self.signal_workflow(cr, uid, [production_id], 'button_produce') produced_qty = self._get_produced_qty(cr, uid, production, context=context) @@ -944,7 +944,7 @@ class mrp_production(osv.osv): stock_mov_obj.action_done(cr, uid, [extra_move_id], context=context) self.message_post(cr, uid, production_id, body=_("%s produced") % self._description, context=context) - self.signal_button_produce_done(cr, uid, [production_id]) + self.signal_workflow(cr, uid, [production_id], 'button_produce_done') return True def _costs_generate(self, cr, uid, production): diff --git a/addons/mrp/procurement.py b/addons/mrp/procurement.py index 50650b7b371..06eacc6a1a2 100644 --- a/addons/mrp/procurement.py +++ b/addons/mrp/procurement.py @@ -114,7 +114,7 @@ class procurement_order(osv.osv): procurement.refresh() self.production_order_create_note(cr, uid, procurement, context=context) production_obj.action_compute(cr, uid, [produce_id], properties=[x.id for x in procurement.property_ids]) - production_obj.signal_button_confirm(cr, uid, [produce_id]) + production_obj.signal_workflow(cr, uid, [produce_id], 'button_confirm') else: res[procurement.id] = False self.message_post(cr, uid, [procurement.id], body=_("No BoM exists for this product!"), context=context) diff --git a/addons/mrp/stock.py b/addons/mrp/stock.py index e04c434a741..236fec019dd 100644 --- a/addons/mrp/stock.py +++ b/addons/mrp/stock.py @@ -159,7 +159,7 @@ class StockMove(osv.osv): 'consumed_for': consumed_for}, context=context) self.action_done(cr, uid, res, context=context) production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])]) - production_obj.signal_button_produce(cr, uid, production_ids) + production_obj.signal_workflow(cr, uid, production_ids, button_produce) for new_move in res: if new_move != move.id: #This move is not already there in move lines of production order @@ -181,7 +181,7 @@ class StockMove(osv.osv): #If we are not scrapping our whole move, tracking and lot references must not be removed production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])]) for prod_id in production_ids: - production_obj.signal_button_produce(cr, uid, [prod_id]) + production_obj.signal_workflow(cr, uid, [prod_id], 'button_produce') for new_move in new_moves: production_obj.write(cr, uid, production_ids, {'move_lines': [(4, new_move)]}) res.append(new_move) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 1eec3cb48d4..5c8103c7626 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -497,7 +497,7 @@ class pos_session(osv.osv): _('Error!'), _("You cannot confirm all orders of this session, because they have not the 'paid' status")) else: - pos_order_obj.signal_done(cr, uid, [order.id]) + pos_order_obj.signal_workflow(cr, uid, [order.id], 'done') return True @@ -581,14 +581,14 @@ class pos_order(osv.osv): order_ids.append(order_id) try: - self.signal_paid(cr, uid, [order_id]) + self.signal_workflow(cr, uid, [order_id], 'paid') except Exception as e: _logger.error('Could not fully process the POS Order: %s', tools.ustr(e)) if to_invoice: self.action_invoice(cr, uid, [order_id], context) order_obj = self.browse(cr, uid, order_id, context) - self.pool['account.invoice'].signal_invoice_open(cr, uid, [order_obj.invoice_id.id]) + self.pool['account.invoice'].signal_workflow(cr, uid, [order_obj.invoice_id.id], 'invoice_open') return order_ids @@ -933,8 +933,8 @@ class pos_order(osv.osv): inv_line['invoice_line_tax_id'] = [(6, 0, [x.id for x in line.product_id.taxes_id] )] inv_line_ref.create(cr, uid, inv_line, context=context) inv_ref.button_reset_taxes(cr, uid, [inv_id], context=context) - self.signal_invoice(cr, uid, [order.id]) - inv_ref.signal_validate(cr, uid, [inv_id]) + self.signal_workflow(cr, uid, [order.id], 'invoice') + inv_ref.signal_workflow(cr, uid, [inv_id], 'validate') if not inv_ids: return {} diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index e8622de493d..da14954d4a0 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -328,7 +328,7 @@ class purchase_order(osv.osv): raise osv.except_osv(_('Invalid Action!'), _('In order to delete a purchase order, you must cancel it first.')) # automatically sending subflow.delete upon deletion - self.signal_purchase_cancel(cr, uid, unlink_ids) + self.signal_workflow(cr, uid, unlink_ids, 'purchase_cancel') return super(purchase_order, self).unlink(cr, uid, unlink_ids, context=context) @@ -519,7 +519,7 @@ class purchase_order(osv.osv): This function prints the request for quotation and mark it as sent, so that we can see more easily the next step of the workflow ''' assert len(ids) == 1, 'This option should only be used for a single id at a time' - self.signal_send_rfq(cr, uid, ids) + self.signal_workflow(cr, uid, ids, 'send_rfq') return self.pool['report'].get_action(cr, uid, ids, 'purchase.report_purchasequotation', context=context) def wkf_confirm_order(self, cr, uid, ids, context=None): @@ -680,19 +680,19 @@ class purchase_order(osv.osv): _('Unable to cancel the purchase order %s.') % (purchase.name), _('First cancel all receptions related to this purchase order.')) self.pool.get('stock.picking') \ - .signal_button_cancel(cr, uid, map(attrgetter('id'), purchase.picking_ids)) + .signal_workflow(cr, uid, map(attrgetter('id'), purchase.picking_ids), 'button_cancel') for inv in purchase.invoice_ids: if inv and inv.state not in ('cancel', 'draft'): raise osv.except_osv( _('Unable to cancel this purchase order.'), _('You must first cancel all invoices related to this purchase order.')) self.pool.get('account.invoice') \ - .signal_invoice_cancel(cr, uid, map(attrgetter('id'), purchase.invoice_ids)) + .signal_workflow(cr, uid, map(attrgetter('id'), purchase.invoice_ids), 'invoice_cancel') self.pool['purchase.order.line'].write(cr, uid, [l.id for l in purchase.order_line], {'state': 'cancel'}) self.write(cr, uid, ids, {'state': 'cancel'}) self.set_order_line_status(cr, uid, ids, 'cancel', context=context) - self.signal_purchase_cancel(cr, uid, ids) + self.signal_workflow(cr, uid, ids, 'purchase_cancel') return True def _prepare_order_line_move(self, cr, uid, order, order_line, picking_id, group_id, context=None): @@ -933,7 +933,7 @@ class purchase_order(osv.osv): # make triggers pointing to the old orders point to the new order for old_id in old_ids: self.redirect_workflow(cr, uid, [(old_id, neworder_id)]) - self.signal_purchase_cancel(cr, uid, [old_id]) + self.signal_workflow(cr, uid, [old_id], 'purchase_cancel') return orders_info @@ -1370,7 +1370,7 @@ class mail_mail(osv.Model): if mail_sent and mail.model == 'purchase.order': obj = self.pool.get('purchase.order').browse(cr, uid, mail.res_id, context=context) if obj.state == 'draft': - self.pool.get('purchase.order').signal_send_rfq(cr, uid, [mail.res_id]) + self.pool.get('purchase.order').signal_workflow(cr, uid, [mail.res_id], 'send_rfq') return super(mail_mail, self)._postprocess_sent_message(cr, uid, mail=mail, context=context, mail_sent=mail_sent) @@ -1423,7 +1423,7 @@ class mail_compose_message(osv.Model): context = context or {} if context.get('default_model') == 'purchase.order' and context.get('default_res_id'): context = dict(context, mail_post_autofollow=True) - self.pool.get('purchase.order').signal_send_rfq(cr, uid, [context['default_res_id']]) + self.pool.get('purchase.order').signal_workflow(cr, uid, [context['default_res_id']], 'send_rfq') return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context) diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py index a4908f23687..954547cb5b5 100644 --- a/addons/purchase_requisition/purchase_requisition.py +++ b/addons/purchase_requisition/purchase_requisition.py @@ -245,7 +245,7 @@ class purchase_requisition(osv.osv): for quotation in tender.purchase_ids: if (self.check_valid_quotation(cr, uid, quotation, context=context)): #use workflow to set PO state to confirm - po.signal_purchase_confirm(cr, uid, [quotation.id]) + po.signal_workflow(cr, uid, [quotation.id], 'purchase_confirm') #get other confirmed lines per supplier for po_line in tender.po_line_ids: @@ -268,13 +268,13 @@ class purchase_requisition(osv.osv): vals = self._prepare_po_line_from_tender(cr, uid, tender, line, new_po, context=context) poline.copy(cr, uid, line.id, default=vals, context=context) #use workflow to set new PO state to confirm - po.signal_purchase_confirm(cr, uid, [new_po]) + po.signal_workflow(cr, uid, [new_po], 'purchase_confirm') #cancel other orders self.cancel_unconfirmed_quotations(cr, uid, tender, context=context) #set tender to state done - self.signal_done(cr, uid, [tender.id]) + self.signal_workflow(cr, uid, [tender.id], 'done') return True def cancel_unconfirmed_quotations(self, cr, uid, tender, context=None): @@ -282,7 +282,7 @@ class purchase_requisition(osv.osv): po = self.pool.get('purchase.order') for quotation in tender.purchase_ids: if quotation.state in ['draft', 'sent', 'bid']: - self.pool.get('purchase.order').signal_purchase_cancel(cr, uid, [quotation.id]) + self.pool.get('purchase.order').signal_workflow(cr, uid, [quotation.id], 'purchase_cancel') po.message_post(cr, uid, [quotation.id], body=_('Cancelled by the call for bids associated to this request for quotation.'), context=context) return True diff --git a/addons/sale/wizard/sale_make_invoice.py b/addons/sale/wizard/sale_make_invoice.py index 170259824f7..073c2481160 100644 --- a/addons/sale/wizard/sale_make_invoice.py +++ b/addons/sale/wizard/sale_make_invoice.py @@ -60,7 +60,7 @@ class sale_make_invoice(osv.osv_memory): for i in o.invoice_ids: newinv.append(i.id) # Dummy call to workflow, will not create another invoice but bind the new invoice to the subflow - order_obj.signal_manual_invoice(cr, uid, [o.id for o in orders if o.order_policy == 'manual']) + order_obj.signal_workflow(cr, uid, [o.id for o in orders if o.order_policy == 'manual'], 'manual_invoice') result = mod_obj.get_object_reference(cr, uid, 'account', 'action_invoice_tree1') id = result and result[1] or False result = act_obj.read(cr, uid, [id], context=context)[0] diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py index 61183413a33..20bac48c562 100644 --- a/addons/sale_stock/sale_stock.py +++ b/addons/sale_stock/sale_stock.py @@ -159,7 +159,7 @@ class sale_order(osv.osv): raise osv.except_osv( _('Cannot cancel sales order!'), _('You must first cancel all delivery order(s) attached to this sales order.')) - stock_obj.signal_button_cancel(cr, uid, [p.id for p in sale.picking_ids]) + stock_obj.signal_workflow(cr, uid, [p.id for p in sale.picking_ids], 'button_cancel') return super(sale_order, self).action_cancel(cr, uid, ids, context=context) def action_wait(self, cr, uid, ids, context=None): @@ -356,7 +356,7 @@ class stock_move(osv.osv): if move.procurement_id and move.procurement_id.sale_line_id: sale_ids.append(move.procurement_id.sale_line_id.order_id.id) if sale_ids: - self.pool.get('sale.order').signal_ship_except(cr, uid, sale_ids) + self.pool.get('sale.order').signal_workflow(cr, uid, sale_ids, 'ship_except') return super(stock_move, self).action_cancel(cr, uid, ids, context=context) def _create_invoice_line_from_vals(self, cr, uid, move, invoice_line_vals, context=None): diff --git a/addons/sale_stock/test/cancel_order_sale_stock.yml b/addons/sale_stock/test/cancel_order_sale_stock.yml index 302cbf4a933..49835b064c9 100644 --- a/addons/sale_stock/test/cancel_order_sale_stock.yml +++ b/addons/sale_stock/test/cancel_order_sale_stock.yml @@ -76,7 +76,7 @@ !python {model: sale.order}: | invoice_ids = self.browse(cr, uid, ref("sale.sale_order_8")).invoice_ids first_invoice_id = invoice_ids[0] - self.pool.get('account.invoice').signal_invoice_cancel(cr, uid, [first_invoice_id.id]) + self.pool.get('account.invoice').signal_workflow(cr, uid, [first_invoice_id.id], 'invoice_cancel') - I check order status in "Invoice Exception" and related invoice is in cancel state. - diff --git a/addons/sale_stock/test/picking_order_policy.yml b/addons/sale_stock/test/picking_order_policy.yml index 57ba0323b4c..8de8ea45a08 100644 --- a/addons/sale_stock/test/picking_order_policy.yml +++ b/addons/sale_stock/test/picking_order_policy.yml @@ -190,7 +190,7 @@ so = self.browse(cr, uid, ref("sale_order_service")) account_invoice_obj = self.pool.get('account.invoice') for invoice in so.invoice_ids: - account_invoice_obj.signal_invoice_open(cr, uid, [invoice.id]) + account_invoice_obj.signal_workflow(cr, uid, [invoice.id], 'invoice_open') - I pay the invoice - diff --git a/addons/stock/wizard/make_procurement_product.py b/addons/stock/wizard/make_procurement_product.py index 73dda6bbb3e..5c6f4b618ee 100644 --- a/addons/stock/wizard/make_procurement_product.py +++ b/addons/stock/wizard/make_procurement_product.py @@ -76,7 +76,7 @@ class make_procurement(osv.osv_memory): 'location_id': wh.lot_stock_id.id, 'company_id': wh.company_id.id, }) - procurement_obj.signal_button_confirm(cr, uid, [procure_id]) + procurement_obj.signal_workflow(cr, uid, [procure_id], 'button_confirm') id2 = data_obj._get_id(cr, uid, 'procurement', 'procurement_tree_view') id3 = data_obj._get_id(cr, uid, 'procurement', 'procurement_form_view') diff --git a/addons/website_quote/controllers/main.py b/addons/website_quote/controllers/main.py index 522a613bdff..8fab07fcce2 100644 --- a/addons/website_quote/controllers/main.py +++ b/addons/website_quote/controllers/main.py @@ -65,7 +65,7 @@ class sale_quote(http.Controller): if token != order.access_token: return request.website.render('website.404') attachments=sign and [('signature.png', sign.decode('base64'))] or [] - order_obj.signal_order_confirm(request.cr, SUPERUSER_ID, [order_id], context=request.context) + order_obj.signal_workflow(request.cr, SUPERUSER_ID, [order_id], 'order_confirm', context=request.context) message = _('Order signed by %s') % (signer,) self.__message_post(message, order_id, type='comment', subtype='mt_comment', attachments=attachments) return True diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index 7ac8783ddd9..c9195d93d12 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -848,9 +848,9 @@ class ir_actions_server(osv.osv): def run_action_trigger(self, cr, uid, action, eval_context=None, context=None): """ Trigger a workflow signal, depending on the use_relational_model: - - `base`: base_model_pool.signal_(cr, uid, context.get('active_id')) + - `base`: base_model_pool.signal_workflow(cr, uid, context.get('active_id'), ) - `relational`: find the related model and object, using the relational - field, then target_model_pool.signal_(cr, uid, target_id) + field, then target_model_pool.signal_workflow(cr, uid, target_id, ) """ obj_pool = self.pool[action.model_id.model] if action.use_relational_model == 'base': diff --git a/openerp/models.py b/openerp/models.py index 86d702ef582..a87ab3e8b23 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -4937,19 +4937,6 @@ class BaseModel(object): """ stuff to do right after the registry is built """ pass - def __getattr__(self, name): - if name.startswith('signal_'): - # self.signal_XXX() sends signal XXX to the record's workflow - signal_name = name[7:] - assert signal_name - return (lambda *args, **kwargs: - self.signal_workflow(*args, signal=signal_name, **kwargs)) - - get = getattr(super(BaseModel, self), '__getattr__', None) - if get is None: - raise AttributeError("%r has no attribute %r" % (type(self).__name__, name)) - return get(name) - def _patch_method(self, name, method): """ Monkey-patch a method for all instances of this model. This replaces the method called `name` by `method` in `self`'s class. From 630580df7f9e9e8d82799fd138fb2ecb49a77a2c Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Mon, 7 Jul 2014 13:12:37 +0200 Subject: [PATCH 02/21] [FIX] signal_XXX -> signal_workflow: forgot quotes around signal name --- addons/mrp/stock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mrp/stock.py b/addons/mrp/stock.py index 236fec019dd..2718b77d051 100644 --- a/addons/mrp/stock.py +++ b/addons/mrp/stock.py @@ -159,7 +159,7 @@ class StockMove(osv.osv): 'consumed_for': consumed_for}, context=context) self.action_done(cr, uid, res, context=context) production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])]) - production_obj.signal_workflow(cr, uid, production_ids, button_produce) + production_obj.signal_workflow(cr, uid, production_ids, 'button_produce') for new_move in res: if new_move != move.id: #This move is not already there in move lines of production order From ac282e029420ec5ad44cc6c3b99784d24faa043b Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 7 Jul 2014 13:51:53 +0200 Subject: [PATCH 03/21] [FIX] unsafe listification of weakref in Python < 2.7.4 Fixes #966 * As a preallocation optimization, ``list()`` calls ``__len__`` on its parameter if it's available * Before Python 2.7.4, WeakSet has a bug[0] where ``len()`` is unsafe: it is done by iteration and weakrefs may be removed from the underlying set during the iteration As a result, the safety feature of listifying a WeakSet to ensure we have strong refs on all items during iteration may blow up. Wrapping the weakset in a ``iter()`` makes ``__len__()`` invisible and ensures we're within the IterationGuard[1]. Which now that I think about it means we *should* be able to safely iterate weaksets in the first place and may not have needed to listify them... [0] http://bugs.python.org/issue14159 [1] http://hg.python.org/cpython/file/b6acfbe2bdbe/Lib/_weakrefset.py#l58 --- openerp/api.py | 4 ++-- openerp/models.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openerp/api.py b/openerp/api.py index 5c8b93892b7..6c70910633f 100644 --- a/openerp/api.py +++ b/openerp/api.py @@ -756,7 +756,7 @@ class Environment(object): """ if not spec: return - for env in list(self.all): + for env in list(iter(self.all)): c = env.cache for field, ids in spec: if ids is None: @@ -769,7 +769,7 @@ class Environment(object): def invalidate_all(self): """ Clear the cache of all environments. """ - for env in list(self.all): + for env in list(iter(self.all)): env.cache.clear() env.prefetch.clear() env.computed.clear() diff --git a/openerp/models.py b/openerp/models.py index 9e289965733..9307b82cee9 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -5415,7 +5415,7 @@ class BaseModel(object): """ If `field` must be recomputed on some record in `self`, return the corresponding records that must be recomputed. """ - for env in [self.env] + list(self.env.all): + for env in [self.env] + list(iter(self.env.all)): if env.todo.get(field) and env.todo[field] & self: return env.todo[field] @@ -5437,7 +5437,7 @@ class BaseModel(object): """ Recompute stored function fields. The fields and records to recompute have been determined by method :meth:`modified`. """ - for env in list(self.env.all): + for env in list(iter(self.env.all)): while env.todo: field, recs = next(env.todo.iteritems()) # evaluate the fields to recompute, and save them to database From efe910569acddfd0f36b5bece1875fd50a4b33d6 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 7 Jul 2014 14:01:07 +0200 Subject: [PATCH 04/21] [REM] unecessary calls to list() * Either further operations don't really care (e.g. ``str.join`` takes any iterable) * Or they do their own seq (``browse`` calls ``tuple()`` on iterable params) --- openerp/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/models.py b/openerp/models.py index 9e289965733..d4b91f4c4ad 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -3013,7 +3013,7 @@ class BaseModel(object): if not fields: fields = filter(valid, self._fields) else: - invalid_fields = list(set(filter(lambda name: not valid(name), fields))) + invalid_fields = set(filter(lambda name: not valid(name), fields)) if invalid_fields: _logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s, fields: %s', operation, user, self._name, ', '.join(invalid_fields)) @@ -3281,7 +3281,7 @@ class BaseModel(object): self._cr.execute(query, (self._name, tuple(self.ids))) res = self._cr.dictfetchall() - uids = list(set(r[k] for r in res for k in ['write_uid', 'create_uid'] if r.get(k))) + uids = set(r[k] for r in res for k in ['write_uid', 'create_uid'] if r.get(k)) names = dict(self.env['res.users'].browse(uids).name_get()) for r in res: @@ -3504,7 +3504,7 @@ class BaseModel(object): for order, obj_name, store_ids, fields in result_store: if obj_name == self._name: - effective_store_ids = list(set(store_ids) - set(ids)) + effective_store_ids = set(store_ids) - set(ids) else: effective_store_ids = store_ids if effective_store_ids: From 94d65557fcd8d8e5fa6344069993b585302692c0 Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Mon, 7 Jul 2014 11:48:31 +0200 Subject: [PATCH 05/21] [FIX]menu bar: clean up css and few cosmetics --- .../static/src/css/attendance.css | 2 +- addons/im_chat/static/src/css/im_chat.css | 1 - addons/web/static/src/css/base.css | 180 ++++-------------- addons/web/static/src/css/base.sass | 137 +++---------- 4 files changed, 65 insertions(+), 255 deletions(-) diff --git a/addons/hr_attendance/static/src/css/attendance.css b/addons/hr_attendance/static/src/css/attendance.css index 2346e2b3a2d..767cfbc061a 100644 --- a/addons/hr_attendance/static/src/css/attendance.css +++ b/addons/hr_attendance/static/src/css/attendance.css @@ -1,3 +1,3 @@ .oe_systray #oe_attendance_sign_in_out_icon { - font-color: white; + color: white; } \ No newline at end of file diff --git a/addons/im_chat/static/src/css/im_chat.css b/addons/im_chat/static/src/css/im_chat.css index ce32d7d4597..6f76ceae193 100644 --- a/addons/im_chat/static/src/css/im_chat.css +++ b/addons/im_chat/static/src/css/im_chat.css @@ -14,7 +14,6 @@ /* button */ .oe_systray #oe_topbar_imbutton_icon { color: white; - margin-right: 10px; } .oe_topbar_item.oe_topbar_imbutton .oe_e { position: relative; diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index acdcc0a95aa..b76bee29a59 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -8,7 +8,6 @@ font-weight: normal; font-style: normal; } - @font-face { font-family: "EntypoRegular"; src: url("/web/static/src/font/entypo-webfont.eot") format("eot"); @@ -19,7 +18,6 @@ font-weight: normal; font-style: normal; } - #oe_main_menu_navbar { min-height: 34px; z-index: 1001; @@ -39,6 +37,39 @@ margin-bottom: 0px; } +.oe_systray { + margin-right: 0px !important; +} + +.oe_notification { + z-index: 1600; +} + +.oe_webclient_timezone_notification a { + color: white; + text-decoration: underline; +} +.oe_webclient_timezone_notification p { + margin-top: 1em; +} +.oe_webclient_timezone_notification dt { + font-weight: bold; +} + +.oe_timezone_systray span { + margin-top: 1px; + background-color: #f6cf3b; +} + +.navbar .oe_topbar_name { + max-width: 150px; + display: inline-block; + height: 15px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + .openerp { padding: 0; margin: 0; @@ -800,23 +831,6 @@ border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; } -.openerp .oe_notification { - z-index: 1500; -} -.openerp .oe_webclient_timezone_notification a { - color: white; - text-decoration: underline; -} -.openerp .oe_webclient_timezone_notification p { - margin-top: 1em; -} -.openerp .oe_webclient_timezone_notification dt { - font-weight: bold; -} -.openerp .oe_timezone_systray span { - margin-top: 1px; - background-color: #f6cf3b; -} .openerp .oe_dialog_warning { width: 100%; } @@ -847,133 +861,6 @@ .openerp .oe_content_full_screen .oe_topbar, .openerp .oe_content_full_screen .oe_leftbar { display: none; } -.openerp .navbar .oe_topbar_anonymous_login { - background-color: #dc5f59; - color: #eeeeee; - background-color: #be4343; - background-image: -webkit-gradient(linear, left top, left bottom, from(#fc8787), to(maroon)); - background-image: -webkit-linear-gradient(top, #fc8787, maroon); - background-image: -moz-linear-gradient(top, #fc8787, maroon); - background-image: -ms-linear-gradient(top, #fc8787, maroon); - background-image: -o-linear-gradient(top, #fc8787, maroon); - background-image: linear-gradient(to bottom, #fc8787, #800000); -} -.openerp .navbar .oe_topbar_anonymous_login a { - display: block; - padding: 5px 10px 7px; - line-height: 20px; - height: 30px; - text-decoration: none; - color: white; - background: transparent; - -webkit-transition: all 0.2s ease-out; - -moz-transition: all 0.2s ease-out; - -ms-transition: all 0.2s ease-out; - -o-transition: all 0.2s ease-out; - transition: all 0.2s ease-out; -} -.openerp .navbar .oe_topbar_anonymous_login a:hover { - background: rgba(0, 0, 0, 0.1); - color: white; - text-shadow: 0px 0px 3px rgba(0, 0, 0, 0.2); - -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset; -} -.openerp .navbar .oe_topbar_item { - display: block; - padding: 5px 10px 26px; - line-height: 20px; - height: 20px; - text-decoration: none; - color: #eeeeee; - vertical-align: top; - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); - -webkit-transition: all 0.2s ease-out; - -moz-transition: all 0.2s ease-out; - -ms-transition: all 0.2s ease-out; - -o-transition: all 0.2s ease-out; - transition: all 0.2s ease-out; -} -.openerp .navbar .oe_topbar_item:hover { - background: rgba(0, 0, 0, 0.2); - text-shadow: black 0px 0px 3px; - color: white; - -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; -} -.openerp .navbar .oe_topbar_avatar { - width: 24px; - height: 24px; - margin: -2px 2px 0 0; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; -} -.openerp .navbar .oe_topbar_avatar { - vertical-align: top; -} -.openerp .navbar .oe_dropdown_arrow:after { - border-top: 4px solid white; -} -.openerp .navbar .dropdown_menu { - top: 32px; - background: #333333; - background: rgba(37, 37, 37, 0.9); - border-color: #999999; - border-color: rgba(0, 0, 0, 0.2); - border-style: solid; - border-width: 0 2px 1px; - -moz-border-radius: 0 0 6px 6px; - -webkit-border-radius: 0 0 6px 6px; - border-radius: 0 0 6px 6px; - -webkit-background-clip: padding-box; - -moz-background-clip: padding-box; - background-clip: padding-box; -} -.openerp .navbar .dropdown_menu li { - float: none; - padding: 3px 12px; -} -.openerp .navbar .dropdown_menu li a { - color: #eeeeee; -} -.openerp .navbar .dropdown_menu li:hover { - background-color: #212121; - background-image: -webkit-gradient(linear, left top, left bottom, from(#292929), to(#191919)); - background-image: -webkit-linear-gradient(top, #292929, #191919); - background-image: -moz-linear-gradient(top, #292929, #191919); - background-image: -ms-linear-gradient(top, #292929, #191919); - background-image: -o-linear-gradient(top, #292929, #191919); - background-image: linear-gradient(to bottom, #292929, #191919); - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} -.openerp .navbar .oe_topbar_name { - max-width: 150px; - display: inline-block; - height: 15px; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} -.openerp .nav li a { - padding: 6px 8px 4px 8px; -} -.openerp .oe_user_menu { - float: right; - padding: 0; - margin: 0; -} -.openerp .oe_user_menu li { - list-style-type: none; - float: left; -} -.openerp .oe_user_menu .oe_dropdown_menu { - right: -1px; -} .openerp .oe_leftbar { display: none; width: 220px; @@ -3114,7 +3001,6 @@ top: 0px; } } - .kitten-mode-activated { background-size: cover; background-attachment: fixed; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index b806c32ffc2..f1f948170e5 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -158,6 +158,37 @@ $sheet-padding: 16px #oe_main_menu_navbar.navbar margin-bottom: 0px +.oe_systray + margin-right: 0px !important + +// Notifications {{{ +.oe_notification + z-index: 1600 + +.oe_webclient_timezone_notification + a + color: white + text-decoration: underline + p + margin-top: 1em + dt + font-weight: bold +.oe_timezone_systray + span + margin-top: 1px + background-color: #f6cf3b +// }}} +// Top Menu {{{ +.navbar + .oe_topbar_name + max-width: 150px + display: inline-block + height: 15px // Customize to manage caret when topbar_name is over sized. + text-overflow: ellipsis + white-space: nowrap + overflow: hidden // Customize to bring ellipsis when topbar_name is over sized. +// }}} + .openerp // Global style {{{ padding: 0 @@ -690,23 +721,6 @@ $sheet-padding: 16px border-bottom-right-radius: 8px border-bottom-left-radius: 8px // }}} - // Notifications {{{ - .oe_notification - z-index: 1500 - - .oe_webclient_timezone_notification - a - color: white - text-decoration: underline - p - margin-top: 1em - dt - font-weight: bold - .oe_timezone_systray - span - margin-top: 1px - background-color: #f6cf3b - // }}} // CrashManager {{{ .oe_dialog_warning width: 100% @@ -738,95 +752,6 @@ $sheet-padding: 16px .oe_topbar, .oe_leftbar display: none // }}} - // Top Menu {{{ - .navbar - .oe_topbar_anonymous_login - background-color: #dc5f59 - color: #eeeeee - @include vertical-gradient(#FC8787, #800000) - a - display: block - padding: 5px 10px 7px - line-height: 20px - height: 30px - text-decoration: none - color: white - background: transparent - @include transition(all 0.2s ease-out) - a:hover - background: rgba(0,0,0,0.1) - color: white - text-shadow: 0px 0px 3px rgba(0,0,0,0.2) - @include box-shadow(0 1px 2px rgba(0,0,0,0.2) inset) - .oe_topbar_item - display: block - padding: 5px 10px 26px - line-height: 20px - height: 20px - text-decoration: none - color: #eee - vertical-align: top - text-shadow: 0 1px 1px rgba(0,0,0,0.2) - @include transition(all 0.2s ease-out) - &:hover - background: rgba(0,0,0,0.2) - text-shadow: black 0px 0px 3px - color: white - @include box-shadow(0 1px 2px rgba(0,0,0,0.4) inset) - - .oe_topbar_avatar - width: 24px - height: 24px - margin: -2px 2px 0 0 - @include radius(3px) - .oe_topbar_avatar - vertical-align: top - .oe_dropdown_arrow:after - border-top: 4px solid white - .dropdown_menu - top: 32px - background: #333 - background: rgba(37,37,37,0.9) - border-color: #999 - border-color: rgba(0, 0, 0, 0.2) - border-style: solid - border-width: 0 2px 1px - @include radius(0 0 6px 6px) - @include background-clip() - li - float: none - padding: 3px 12px - a - color: #eee - &:hover - @include vertical-gradient(#292929, #191919) - @include box-shadow(none) - .oe_topbar_name - max-width: 150px - display: inline-block - height: 15px // Customize to manage caret when topbar_name is over sized. - text-overflow: ellipsis - white-space: nowrap - overflow: hidden // Customize to bring ellipsis when topbar_name is over sized. - - // oe menu is the list of the buttons on the left side of the bar. - // So why aren't the buttons oe_topbar_items ? This sad state of affairs - // is a leftover from an uncomplete refactoring when the left and top menu - // were merged. You are welcome to refactor and clean this up - .nav li a - padding: 6px 8px 4px 8px - - .oe_user_menu - float: right - padding: 0 - margin: 0 - li - list-style-type: none - float: left - .oe_dropdown_menu - right: -1px - - // }}} // Webclient.leftbar {{{ .oe_leftbar display: none From ccc211fa51ef6cc136e651cfc2f1e649375382d7 Mon Sep 17 00:00:00 2001 From: krnkris Date: Mon, 7 Jul 2014 10:18:58 +0200 Subject: [PATCH 06/21] [I18N] base: fix Hungarian translations (Manual rebase of #956, pending update in Launchpad translations) --- openerp/addons/base/i18n/hu.po | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openerp/addons/base/i18n/hu.po b/openerp/addons/base/i18n/hu.po index f4e7144d762..4cf4f5d05f1 100644 --- a/openerp/addons/base/i18n/hu.po +++ b/openerp/addons/base/i18n/hu.po @@ -8,7 +8,7 @@ msgstr "" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-12-21 17:04+0000\n" "PO-Revision-Date: 2013-11-24 14:57+0000\n" -"Last-Translator: krnkris \n" +"Last-Translator: krnkris \n" "Language-Team: Hungarian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -103,7 +103,7 @@ msgstr "Indiai bérszámfejtés" #: help:ir.cron,model:0 msgid "" "Model name on which the method to be called is located, e.g. 'res.partner'." -msgstr "Modell név ahogyan az megnevezve megtalálható, p.l. 'res.partner'." +msgstr "Modell név elnevezése szerint megtalálható, p.l. 'res.partner'." #. module: base #: view:ir.module.module:0 @@ -4000,7 +4000,7 @@ msgstr "Időszakos értékelések, Felbecsülések, Felmérések" #: model:ir.actions.act_window,name:base.ir_property_form #: model:ir.ui.menu,name:base.menu_ir_property_form_all msgid "Configuration Parameters" -msgstr "Konfiguráció Paraméterek" +msgstr "Beállítási Paaraméterek" #. module: base #: constraint:ir.cron:0 @@ -4171,9 +4171,9 @@ msgid "" "separated list of valid field names (optionally followed by asc/desc for the " "direction)" msgstr "" -"Invalid \"order\" specified. A valid \"order\" specification is a comma-" -"separated list of valid field names (optionally followed by asc/desc for the " -"direction)" +"Érvénytelen \"megrendelés\" meghatározás. Az érvénnyes \"megrendelés\" egy vesszővel" +"elválasztott lista érvényes mező nevekkel (választhatóan növekvő/csökkenő sorrendbe " +"állítva)" #. module: base #: model:ir.model,name:base.model_ir_module_module_dependency @@ -4217,7 +4217,7 @@ msgstr "" #. module: base #: model:res.groups,name:base.group_survey_user msgid "Survey / User" -msgstr "Felmérés / felhasználó" +msgstr "Felmérés / Felhasználó" #. module: base #: view:ir.module.module:0 From 47dd44be4305efe08f2f287e8228c5f892fad4c8 Mon Sep 17 00:00:00 2001 From: jkei Date: Mon, 7 Jul 2014 14:24:05 +0200 Subject: [PATCH 07/21] [FIX] account: typo in get_fiscal_position() Fixes #971: File "/Users/keje/src/odoo/addons/account/partner.py", line 107, in get_fiscal_position return part.property_account_position.id NameError: global name 'part' is not defined --- addons/account/partner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account/partner.py b/addons/account/partner.py index afbd9114c10..8c47967b44c 100644 --- a/addons/account/partner.py +++ b/addons/account/partner.py @@ -104,7 +104,7 @@ class account_fiscal_position(osv.osv): # partner manually set fiscal position always win if partner.property_account_position: - return part.property_account_position.id + return partner.property_account_position.id # if no delivery use invocing if delivery_id: From 139d868c9403b529e4a3d512e480208853e66246 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Mon, 7 Jul 2014 15:25:07 +0200 Subject: [PATCH 08/21] [REL] 8.0 RC1 --- openerp/release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/release.py b/openerp/release.py index 1a9c863660c..8cc3235bfdf 100644 --- a/openerp/release.py +++ b/openerp/release.py @@ -30,7 +30,7 @@ RELEASE_LEVELS_DISPLAY = {ALPHA: ALPHA, # properly comparable using normal operarors, for example: # (6,1,0,'beta',0) < (6,1,0,'candidate',1) < (6,1,0,'candidate',2) # (6,1,0,'candidate',2) < (6,1,0,'final',0) < (6,1,2,'final',0) -version_info = (8, 0, 0, ALPHA, 1) +version_info = (8, 0, 0, RELEASE_CANDIDATE, 1) version = '.'.join(map(str, version_info[:2])) + RELEASE_LEVELS_DISPLAY[version_info[3]] + str(version_info[4] or '') series = serie = major_version = '.'.join(map(str, version_info[:2])) From 09f094ff7f8659536f50862373bb50c30f097b3e Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Mon, 7 Jul 2014 15:47:27 +0200 Subject: [PATCH 09/21] [FIX] models: reorganize model instantiation, which was broken when adding custom fields --- openerp/models.py | 147 ++++++++++++++++++++++++---------------------- 1 file changed, 76 insertions(+), 71 deletions(-) diff --git a/openerp/models.py b/openerp/models.py index 6e095049dd2..bcb6e80fcac 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -629,76 +629,8 @@ class BaseModel(object): if col._type == 'float': cls._columns[key] = copy.copy(col) - # link the class to the registry, and update the registry - cls.pool = pool - # Note: we have to insert an instance into the registry now, because it - # can trigger some stuff on other models which expect this new instance - # (like method _inherits_reload_src()) + # instantiate the model, and initialize it model = object.__new__(cls) - cls._model = model # backward compatibility - pool.add(name, model) - - # determine description, table, sequence and log_access - if not cls._description: - cls._description = cls._name - if not cls._table: - cls._table = cls._name.replace('.', '_') - if not cls._sequence: - cls._sequence = cls._table + '_id_seq' - if not hasattr(cls, '_log_access'): - # If _log_access is not specified, it is the same value as _auto. - cls._log_access = cls._auto - - # Transience - if cls.is_transient(): - cls._transient_check_count = 0 - cls._transient_max_count = config.get('osv_memory_count_limit') - cls._transient_max_hours = config.get('osv_memory_age_limit') - assert cls._log_access, \ - "TransientModels must have log_access turned on, " \ - "in order to implement their access rights policy" - - # retrieve new-style fields and duplicate them (to avoid clashes with - # inheritance between different models) - cls._fields = {} - for attr, field in getmembers(cls, Field.__instancecheck__): - if not field._origin: - cls._add_field(attr, field.copy()) - - # introduce magic fields - cls._add_magic_fields() - - # register stuff about low-level function fields and custom fields - cls._init_function_fields(pool, cr) - cls._init_manual_fields(pool, cr) - - # process _inherits - cls._inherits_check() - cls._inherits_reload() - - # register constraints and onchange methods - cls._init_constraints_onchanges() - - # check defaults - for k in cls._defaults: - assert k in cls._fields, \ - "Model %s has a default for nonexiting field %s" % (cls._name, k) - - # restart columns - for column in cls._columns.itervalues(): - column.restart() - - # validate rec_name - if cls._rec_name: - assert cls._rec_name in cls._fields, \ - "Invalid rec_name %s for model %s" % (cls._rec_name, cls._name) - elif 'name' in cls._fields: - cls._rec_name = 'name' - - # prepare ormcache, which must be shared by all instances of the model - cls._ormcache = {} - - # complete the initialization of model model.__init__(pool, cr) return model @@ -818,8 +750,81 @@ class BaseModel(object): return None def __init__(self, pool, cr): - # this method no longer does anything; kept for backward compatibility - pass + """ Initialize a model and make it part of the given registry. + + - copy the stored fields' functions in the registry, + - retrieve custom fields and add them in the model, + - ensure there is a many2one for each _inherits'd parent, + - update the children's _columns, + - give a chance to each field to initialize itself. + + """ + cls = type(self) + + # link the class to the registry, and update the registry + cls.pool = pool + cls._model = self # backward compatibility + pool.add(cls._name, self) + + # determine description, table, sequence and log_access + if not cls._description: + cls._description = cls._name + if not cls._table: + cls._table = cls._name.replace('.', '_') + if not cls._sequence: + cls._sequence = cls._table + '_id_seq' + if not hasattr(cls, '_log_access'): + # If _log_access is not specified, it is the same value as _auto. + cls._log_access = cls._auto + + # Transience + if cls.is_transient(): + cls._transient_check_count = 0 + cls._transient_max_count = config.get('osv_memory_count_limit') + cls._transient_max_hours = config.get('osv_memory_age_limit') + assert cls._log_access, \ + "TransientModels must have log_access turned on, " \ + "in order to implement their access rights policy" + + # retrieve new-style fields and duplicate them (to avoid clashes with + # inheritance between different models) + cls._fields = {} + for attr, field in getmembers(cls, Field.__instancecheck__): + if not field._origin: + cls._add_field(attr, field.copy()) + + # introduce magic fields + cls._add_magic_fields() + + # register stuff about low-level function fields and custom fields + cls._init_function_fields(pool, cr) + cls._init_manual_fields(pool, cr) + + # process _inherits + cls._inherits_check() + cls._inherits_reload() + + # register constraints and onchange methods + cls._init_constraints_onchanges() + + # check defaults + for k in cls._defaults: + assert k in cls._fields, \ + "Model %s has a default for nonexiting field %s" % (cls._name, k) + + # restart columns + for column in cls._columns.itervalues(): + column.restart() + + # validate rec_name + if cls._rec_name: + assert cls._rec_name in cls._fields, \ + "Invalid rec_name %s for model %s" % (cls._rec_name, cls._name) + elif 'name' in cls._fields: + cls._rec_name = 'name' + + # prepare ormcache, which must be shared by all instances of the model + cls._ormcache = {} def __export_xml_id(self): """ Return a valid xml_id for the record `self`. """ From e5b093374361bfe0d45cd3e282c69d3133e046ac Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Mon, 7 Jul 2014 15:32:25 +0200 Subject: [PATCH 10/21] [FIX] data-dir management - sessions are now shared between series. - use site data dir instead of user data dir if user has no home dir. - in http and module handling, `data-dir` was used before being initialized, using the default value instead of user input (fixes #308, #904) --- openerp/http.py | 7 +++++-- openerp/modules/module.py | 6 +++++- openerp/tools/config.py | 7 +++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/openerp/http.py b/openerp/http.py index 3c2998699e7..c154330b059 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -1145,11 +1145,14 @@ class Root(object): """Root WSGI application for the OpenERP Web Client. """ def __init__(self): + self._loaded = False + + @lazy_property + def session_store(self): # Setup http sessions path = openerp.tools.config.session_dir _logger.debug('HTTP sessions stored in: %s', path) - self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path, session_class=OpenERPSession) - self._loaded = False + return werkzeug.contrib.sessions.FilesystemSessionStore(path, session_class=OpenERPSession) @lazy_property def nodb_routing_map(self): diff --git a/openerp/modules/module.py b/openerp/modules/module.py index 505e51b0394..72b87e0bbfa 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -43,7 +43,7 @@ MANIFEST = '__openerp__.py' _logger = logging.getLogger(__name__) # addons path as a list -ad_paths = [tools.config.addons_data_dir] +ad_paths = [] hooked = False # Modules already loaded @@ -89,6 +89,10 @@ def initialize_sys_path(): global ad_paths global hooked + dd = tools.config.addons_data_dir + if dd not in ad_paths: + ad_paths.append(dd) + for ad in tools.config['addons_path'].split(','): ad = os.path.abspath(tools.ustr(ad.strip())) if ad not in ad_paths: diff --git a/openerp/tools/config.py b/openerp/tools/config.py index 9a054772695..e4205f0b5f5 100644 --- a/openerp/tools/config.py +++ b/openerp/tools/config.py @@ -61,7 +61,10 @@ def check_ssl(): DEFAULT_LOG_HANDLER = [':INFO'] def _get_default_datadir(): - return appdirs.user_data_dir(appname='OpenERP', appauthor=release.author) + home = os.path.expanduser('~') + func = appdirs.user_data_dir if os.path.exists(home) else appdirs.site_data_dir + # No "version" kwarg as session and filestore paths are shared against series + return func(appname='Odoo', appauthor=release.author) class configmanager(object): def __init__(self, fname=None): @@ -649,7 +652,7 @@ class configmanager(object): @property def session_dir(self): - d = os.path.join(self['data_dir'], 'sessions', release.series) + d = os.path.join(self['data_dir'], 'sessions') if not os.path.exists(d): os.makedirs(d, 0700) else: From a6b025d6d9a10f1f6594e7cd1aa5367b4e6c3fcb Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 8 Jul 2014 10:16:16 +0200 Subject: [PATCH 11/21] [FIX] models, fields: add model dependencies for models backed up by sql views --- addons/hr_timesheet_sheet/hr_timesheet_sheet.py | 5 +++++ openerp/fields.py | 14 +++++++++++--- openerp/models.py | 4 ++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py index a2845171212..7bbcda29c09 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py @@ -532,6 +532,11 @@ class hr_timesheet_sheet_sheet_day(osv.osv): 'total_attendance': fields.float('Attendance', readonly=True), 'total_difference': fields.float('Difference', readonly=True), } + _depends = { + 'hr.analytic.timesheet': ['line_id', 'sheet_id'], + 'account.analytic.line': ['date', 'unit_amount'], + 'hr.attendance': ['name', 'action', 'sheet_id'], + } def init(self, cr): cr.execute("""create or replace view hr_timesheet_sheet_sheet_day as diff --git a/openerp/fields.py b/openerp/fields.py index ede066e37fe..f8b5bd4189c 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -366,9 +366,17 @@ class Field(object): else: self._setup_regular(env) - # put invalidation/recomputation triggers on dependencies + # put invalidation/recomputation triggers on field dependencies + model = env[self.model_name] for path in self.depends: - self._setup_dependency([], env[self.model_name], path.split('.')) + self._setup_dependency([], model, path.split('.')) + + # put invalidation triggers on model dependencies + for dep_model_name, field_names in model._depends.iteritems(): + dep_model = env[dep_model_name] + for field_name in field_names: + field = dep_model._fields[field_name] + field._triggers.add((self, None)) # # Setup of related fields @@ -800,7 +808,7 @@ class Field(object): # invalidate the fields that depend on self, and prepare recomputation spec = [(self, records._ids)] for field, path in self._triggers: - if field.store: + if path and field.store: # don't move this line to function top, see log env = records.env(user=SUPERUSER_ID, context={'active_test': False}) target = env[field.model_name].search([(path, 'in', records.ids)]) diff --git a/openerp/models.py b/openerp/models.py index 6f017b617b1..3b8e1a93648 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -326,6 +326,10 @@ class BaseModel(object): _log_create = False _sql_constraints = [] + # model dependencies, for models backed up by sql views: + # {model_name: field_names, ...} + _depends = {} + CONCURRENCY_CHECK_FIELD = '__last_update' def log(self, cr, uid, id, message, secondary=False, context=None): From 3fdc232352fa348c1a39597cfbcea577afc4e282 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 8 Jul 2014 13:29:45 +0200 Subject: [PATCH 12/21] [FIX] fields: add a type to field Id; this fixes #990 --- openerp/fields.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openerp/fields.py b/openerp/fields.py index ede066e37fe..ce280418e77 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -1542,6 +1542,9 @@ class Id(Field): store = True readonly = True + def __init__(self, string=None, **kwargs): + super(Id, self).__init__(type='integer', string=string, **kwargs) + def to_column(self): return fields.integer('ID') From 2f3aa63617d6b951202cfb9a20bc1d4242444544 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 8 Jul 2014 14:39:24 +0200 Subject: [PATCH 13/21] [FIX] compile of rst module descriptions to HTML lxml (used by the html sanitizer) really does not like documents as unicode strings with an encoding declaration (which doesn't make sense indeed), yet docutils with output_encoding=unicode generates one (a stupid one too, says "encoding=unicode"). Disable the xml declaration altogether when compiling rST module declarations to HTML. --- openerp/addons/base/module/module.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index 89f3e9d006e..4c181575852 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -177,7 +177,12 @@ class module(osv.osv): element.set('src', "/%s/static/description/%s" % (module.name, element.get('src'))) res[module.id] = lxml.html.tostring(html) else: - overrides = dict(embed_stylesheet=False, doctitle_xform=False, output_encoding='unicode') + overrides = { + 'embed_stylesheet': False, + 'doctitle_xform': False, + 'output_encoding': 'unicode', + 'xml_declaration': False, + } output = publish_string(source=module.description, settings_overrides=overrides, writer=MyWriter()) res[module.id] = output return res From 34dac62d32a759d10615fedbf43228d9642567a1 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 8 Jul 2014 14:52:23 +0200 Subject: [PATCH 14/21] [IMP] models: add an extension mechanism for attribute _depends on models --- openerp/models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openerp/models.py b/openerp/models.py index 3b8e1a93648..bd9f4f49272 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -589,6 +589,10 @@ class BaseModel(object): inherits = dict(parent_class._inherits) inherits.update(cls._inherits) + depends = dict(parent_class._depends) + for m, fs in cls._depends.iteritems(): + depends.setdefault(m, []).extend(fs) + old_constraints = parent_class._constraints new_constraints = cls._constraints # filter out from old_constraints the ones overridden by a @@ -608,6 +612,7 @@ class BaseModel(object): '_columns': columns, '_defaults': defaults, '_inherits': inherits, + '_depends': depends, '_constraints': constraints, '_sql_constraints': sql_constraints, } @@ -621,6 +626,7 @@ class BaseModel(object): '_columns': dict(cls._columns), '_defaults': dict(cls._defaults), '_inherits': dict(cls._inherits), + '_depends': dict(cls._depends), '_constraints': list(cls._constraints), '_sql_constraints': list(cls._sql_constraints), '_original_module': original_module, From 2398f480cddb8d3c36bf1336a3d267ba07a2d5f9 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 8 Jul 2014 15:32:38 +0200 Subject: [PATCH 15/21] [IMP] addons: add _depends on models backed up by an sql view and used by other models --- .../account/report/account_invoice_report.py | 18 ++++++++++++++++++ .../account_analytic_analysis.py | 11 +++++++++++ .../wizard/account_followup_print.py | 8 ++++++++ .../report/hr_evaluation_report.py | 8 ++++++++ .../hr_timesheet_sheet/hr_timesheet_sheet.py | 10 ++++++++-- addons/sale/report/invoice_report.py | 3 +++ 6 files changed, 56 insertions(+), 2 deletions(-) diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py index 8fecf85ee0b..7a5ce9c0fb1 100644 --- a/addons/account/report/account_invoice_report.py +++ b/addons/account/report/account_invoice_report.py @@ -98,6 +98,24 @@ class account_invoice_report(osv.osv): } _order = 'date desc' + _depends = { + 'account.invoice': [ + 'account_id', 'amount_total', 'commercial_partner_id', 'company_id', + 'currency_id', 'date_due', 'date_invoice', 'fiscal_position', + 'journal_id', 'partner_bank_id', 'partner_id', 'payment_term', + 'period_id', 'residual', 'state', 'type', 'user_id', + ], + 'account.invoice.line': [ + 'account_id', 'invoice_id', 'price_subtotal', 'product_id', + 'quantity', 'uos_id', + ], + 'product.product': ['product_tmpl_id'], + 'product.template': ['categ_id'], + 'product.uom': ['category_id', 'factor', 'name', 'uom_type'], + 'res.currency.rate': ['currency_id', 'name'], + 'res.partner': ['country_id'], + } + def _select(self): select_str = """ SELECT sub.id, sub.date, sub.product_id, sub.partner_id, sub.country_id, diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py index 3b5819968a3..d1647f77390 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis.py +++ b/addons/account_analytic_analysis/account_analytic_analysis.py @@ -804,6 +804,12 @@ class account_analytic_account_summary_user(osv.osv): 'user': fields.many2one('res.users', 'User'), } + _depends = { + 'res.users': ['id'], + 'account.analytic.line': ['account_id', 'journal_id', 'unit_amount', 'user_id'], + 'account.analytic.journal': ['type'], + } + def init(self, cr): openerp.tools.sql.drop_view_if_exists(cr, 'account_analytic_analysis_summary_user') cr.execute('''CREATE OR REPLACE VIEW account_analytic_analysis_summary_user AS ( @@ -837,6 +843,11 @@ class account_analytic_account_summary_month(osv.osv): 'month': fields.char('Month', size=32, readonly=True), } + _depends = { + 'account.analytic.line': ['account_id', 'date', 'journal_id', 'month', 'unit_amount'], + 'account.analytic.journal': ['type'], + } + def init(self, cr): openerp.tools.sql.drop_view_if_exists(cr, 'account_analytic_analysis_summary_month') cr.execute('CREATE VIEW account_analytic_analysis_summary_month AS (' \ diff --git a/addons/account_followup/wizard/account_followup_print.py b/addons/account_followup/wizard/account_followup_print.py index 83100723edb..550ca753059 100644 --- a/addons/account_followup/wizard/account_followup_print.py +++ b/addons/account_followup/wizard/account_followup_print.py @@ -42,6 +42,14 @@ class account_followup_stat_by_partner(osv.osv): 'company_id': fields.many2one('res.company', 'Company', readonly=True), } + _depends = { + 'account.move.line': [ + 'account_id', 'company_id', 'credit', 'date', 'debit', + 'followup_date', 'followup_line_id', 'partner_id', 'reconcile_id', + ], + 'account.account': ['active', 'type'], + } + def init(self, cr): tools.drop_view_if_exists(cr, 'account_followup_stat_by_partner') # Here we don't have other choice but to create a virtual ID based on the concatenation diff --git a/addons/hr_evaluation/report/hr_evaluation_report.py b/addons/hr_evaluation/report/hr_evaluation_report.py index ce6f381fb60..32ec3f43734 100644 --- a/addons/hr_evaluation/report/hr_evaluation_report.py +++ b/addons/hr_evaluation/report/hr_evaluation_report.py @@ -59,6 +59,14 @@ class hr_evaluation_report(osv.Model): } _order = 'create_date desc' + _depends = { + 'hr.evaluation.interview': ['evaluation_id', 'id', 'request_id'], + 'hr_evaluation.evaluation': [ + 'create_date', 'date', 'date_close', 'employee_id', 'plan_id', + 'rating', 'state', + ], + } + def init(self, cr): tools.drop_view_if_exists(cr, 'hr_evaluation_report') cr.execute(""" diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py index 7bbcda29c09..96be836bff1 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py @@ -533,9 +533,9 @@ class hr_timesheet_sheet_sheet_day(osv.osv): 'total_difference': fields.float('Difference', readonly=True), } _depends = { - 'hr.analytic.timesheet': ['line_id', 'sheet_id'], 'account.analytic.line': ['date', 'unit_amount'], - 'hr.attendance': ['name', 'action', 'sheet_id'], + 'hr.analytic.timesheet': ['line_id', 'sheet_id'], + 'hr.attendance': ['action', 'name', 'sheet_id'], } def init(self, cr): @@ -607,6 +607,12 @@ class hr_timesheet_sheet_sheet_account(osv.osv): 'invoice_rate': fields.many2one('hr_timesheet_invoice.factor', 'Invoice rate', readonly=True), } + _depends = { + 'account.analytic.line': ['account_id', 'date', 'to_invoice', 'unit_amount', 'user_id'], + 'hr.analytic.timesheet': ['line_id'], + 'hr_timesheet_sheet.sheet': ['date_from', 'date_to', 'user_id'], + } + def init(self, cr): cr.execute("""create or replace view hr_timesheet_sheet_sheet_account as ( select diff --git a/addons/sale/report/invoice_report.py b/addons/sale/report/invoice_report.py index c12db9bfdb0..e66daddcfa8 100644 --- a/addons/sale/report/invoice_report.py +++ b/addons/sale/report/invoice_report.py @@ -25,6 +25,9 @@ class account_invoice_report(osv.osv): _columns = { 'section_id': fields.many2one('crm.case.section', 'Sales Team'), } + _depends = { + 'account.invoice': ['section_id'], + } def _select(self): return super(account_invoice_report, self)._select() + ", sub.section_id as section_id" From b05cf32b8f1170f7bbc99b7db9348bc806138b41 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 8 Jul 2014 15:47:44 +0200 Subject: [PATCH 16/21] [IMP] fields: add missing case for invalidating fields, when path is None --- openerp/fields.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/fields.py b/openerp/fields.py index f8b5bd4189c..d4a880c5f43 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -833,9 +833,9 @@ class Field(object): if path == 'id': target = records - computed else: - for record in target.browse(env.cache[field]) - computed: - if record._mapped_cache(path) & records: - target += record + target = target.browse(env.cache[field]) - computed + if path: + target = target.filtered(lambda rec: rec._mapped_cache(path) & records) if target: spec.append((field, target._ids)) From 603bde1b1b5ecb04fd9c65cdf2a279a4e0c50556 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 8 Jul 2014 15:52:02 +0200 Subject: [PATCH 17/21] [IMP] fields: improve code in former commit --- openerp/fields.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openerp/fields.py b/openerp/fields.py index d4a880c5f43..be5b58ffcf4 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -832,10 +832,13 @@ class Field(object): computed = target.browse(env.computed[field]) if path == 'id': target = records - computed + elif path: + target = (target.browse(env.cache[field]) - computed).filtered( + lambda rec: rec._mapped_cache(path) & records + ) else: target = target.browse(env.cache[field]) - computed - if path: - target = target.filtered(lambda rec: rec._mapped_cache(path) & records) + if target: spec.append((field, target._ids)) From 2d39a4fadaeb79155f8397496ddc88dde38e2c7a Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 8 Jul 2014 15:55:42 +0200 Subject: [PATCH 18/21] [FIX] account_analytic_analysis: fix _depends --- addons/account_analytic_analysis/account_analytic_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py index d1647f77390..d0eb8c4765d 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis.py +++ b/addons/account_analytic_analysis/account_analytic_analysis.py @@ -844,7 +844,7 @@ class account_analytic_account_summary_month(osv.osv): } _depends = { - 'account.analytic.line': ['account_id', 'date', 'journal_id', 'month', 'unit_amount'], + 'account.analytic.line': ['account_id', 'date', 'journal_id', 'unit_amount'], 'account.analytic.journal': ['type'], } From faba7cd5aab1e9d5be2937be4ae5424b732b7d72 Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Tue, 8 Jul 2014 17:33:00 +0200 Subject: [PATCH 19/21] [IMP] Crm: Add tracking mixin to manage utm campaign and add this mixin to mass_mailing, crm_lead and sale_order --- addons/crm/__init__.py | 1 + addons/crm/crm.py | 86 +++++++++++++++---- addons/crm/crm_data.xml | 25 +++++- addons/crm/crm_lead.py | 10 +-- addons/crm/crm_lead_data.xml | 33 ------- addons/crm/crm_lead_demo.xml | 46 ---------- addons/crm/crm_lead_view.xml | 9 +- addons/crm/crm_view.xml | 86 ++++++++++++++----- addons/crm/ir_http.py | 13 +++ addons/crm/report/crm_lead_report.py | 10 +-- addons/crm/report/crm_lead_report_view.xml | 10 ++- addons/crm/security/ir.model.access.csv | 12 +-- addons/crm_claim/crm_claim_data.xml | 4 +- addons/crm_helpdesk/crm_helpdesk.py | 2 +- .../report/crm_helpdesk_report.py | 2 +- addons/crm_mass_mailing/__init__.py | 22 +++++ addons/crm_mass_mailing/__openerp__.py | 18 ++++ addons/crm_mass_mailing/mass_mailing.py | 6 ++ addons/crm_mass_mailing/mass_mailing.xml | 23 +++++ addons/crm_mass_mailing/mass_mailing_data.xml | 0 addons/crm_partner_assign/crm_lead_demo.xml | 2 - addons/crm_partner_assign/crm_portal_view.xml | 12 +-- addons/marketing_crm/views/crm.xml | 10 ++- addons/mass_mailing/models/mass_mailing.py | 2 - addons/sale_crm/sale_crm.py | 3 +- addons/sale_crm/sale_crm_view.xml | 6 ++ addons/website/models/ir_http.py | 2 +- addons/website_crm/controllers/main.py | 2 +- 28 files changed, 287 insertions(+), 170 deletions(-) create mode 100644 addons/crm/ir_http.py create mode 100644 addons/crm_mass_mailing/__init__.py create mode 100644 addons/crm_mass_mailing/__openerp__.py create mode 100644 addons/crm_mass_mailing/mass_mailing.py create mode 100644 addons/crm_mass_mailing/mass_mailing.xml create mode 100644 addons/crm_mass_mailing/mass_mailing_data.xml diff --git a/addons/crm/__init__.py b/addons/crm/__init__.py index 872d0390303..1cd494b845f 100644 --- a/addons/crm/__init__.py +++ b/addons/crm/__init__.py @@ -24,6 +24,7 @@ import crm_segmentation import crm_lead import sales_team import calendar_event +import ir_http import crm_phonecall import report import wizard diff --git a/addons/crm/crm.py b/addons/crm/crm.py index 88933aaf2b9..20d025fd64d 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -19,13 +19,8 @@ # ############################################################################## -import calendar -from datetime import date, datetime -from dateutil import relativedelta - -from openerp import tools -from openerp.osv import fields -from openerp.osv import osv +from openerp.osv import osv, fields +from openerp.http import request AVAILABLE_PRIORITIES = [ ('0', 'Very Low'), @@ -35,8 +30,10 @@ AVAILABLE_PRIORITIES = [ ('4', 'Very High'), ] -class crm_case_channel(osv.osv): - _name = "crm.case.channel" + +class crm_tracking_medium(osv.Model): + # OLD crm.case.channel + _name = "crm.tracking.medium" _description = "Channels" _order = 'name' _columns = { @@ -47,6 +44,67 @@ class crm_case_channel(osv.osv): 'active': lambda *a: 1, } + +class crm_tracking_campaign(osv.Model): + # OLD crm.case.resource.type + _name = "crm.tracking.campaign" + _description = "Campaign" + _rec_name = "name" + _columns = { + 'name': fields.char('Campaign Name', required=True, translate=True), + 'section_id': fields.many2one('crm.case.section', 'Sales Team'), + } + + +class crm_tracking_source(osv.Model): + _name = "crm.tracking.source" + _description = "Source" + _rec_name = "name" + _columns = { + 'name': fields.char('Source Name', required=True, translate=True), + } + + +class crm_tracking_mixin(osv.AbstractModel): + """Mixin class for objects which can be tracked by marketing. """ + _name = 'crm.tracking.mixin' + + _columns = { + 'campaign_id': fields.many2one('crm.tracking.campaign', 'Campaign', # old domain ="['|',('section_id','=',section_id),('section_id','=',False)]" + help="This is a name that helps you keep track of your different campaign efforts Example: Fall_Drive, Christmas_Special"), + 'source_id': fields.many2one('crm.tracking.source', 'Source', help="This is the source of the link Example: Search Engine, another domain, or name of email list"), + 'medium_id': fields.many2one('crm.tracking.medium', 'Channel', help="This is the method of delivery. EX: Postcard, Email, or Banner Ad"), + } + + def tracking_fields(self): + return [('utm_campaign', 'campaign_id'), ('utm_source', 'source_id'), ('utm_medium', 'medium_id')] + + def tracking_get_values(self, cr, uid, vals, context=None): + for key, field in self.tracking_fields(): + column = self._all_columns[field].column + value = vals.get(field) or (request and request.session.get(key)) # params.get sould be always in session by the dispatch from ir_http + if column._type in ['many2one'] and isinstance(value, basestring): # if we receive a string for a many2one, we search / create the id + if value: + Model = self.pool[column._obj] + rel_id = Model.name_search(cr, uid, value, context=context) + if not rel_id: + rel_id = Model.create(cr, uid, {'name': value}, context=context) + vals[field] = rel_id + # Here the code for other cases that many2one + else: + vals[field] = value + return vals + + def _get_default_track(self, cr, uid, field, context=None): + return self.tracking_get_values(cr, uid, {}, context=context).get(field) + + _defaults = { + 'source_id': lambda self, cr, uid, ctx: self._get_default_track(cr, uid, 'source_id', ctx), + 'campaign_id': lambda self, cr, uid, ctx: self._get_default_track(cr, uid, 'campaign_id', ctx), + 'medium_id': lambda self, cr, uid, ctx: self._get_default_track(cr, uid, 'medium_id', ctx), + } + + class crm_case_stage(osv.osv): """ Model for case stages. This models the main stages of a document management flow. Main CRM objects (leads, opportunities, project @@ -106,16 +164,6 @@ class crm_case_categ(osv.osv): 'object_id' : _find_object_id } -class crm_case_resource_type(osv.osv): - """ Resource Type of case """ - _name = "crm.case.resource.type" - _description = "Campaign" - _rec_name = "name" - _columns = { - 'name': fields.char('Campaign Name', required=True, translate=True), - 'section_id': fields.many2one('crm.case.section', 'Sales Team'), - } - class crm_payment_mode(osv.osv): """ Payment Mode for Fund """ _name = "crm.payment.mode" diff --git a/addons/crm/crm_data.xml b/addons/crm/crm_data.xml index a5a732747ab..c354e2d10c6 100644 --- a/addons/crm/crm_data.xml +++ b/addons/crm/crm_data.xml @@ -13,18 +13,35 @@ open - + + + + Search engine + + + Mailing Partner + + + Newsletter + + + + Website - + Phone - + Direct - + Email + + Banner Ad + + True diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 7d53c1d51eb..b82f5a4d41f 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -32,13 +32,14 @@ from openerp.tools.translate import _ CRM_LEAD_FIELDS_TO_MERGE = ['name', 'partner_id', - 'channel_id', + 'campaign_id', 'company_id', 'country_id', 'section_id', 'state_id', 'stage_id', - 'type_id', + 'medium_id', + 'source_id', 'user_id', 'title', 'city', @@ -67,7 +68,7 @@ class crm_lead(format_address, osv.osv): _name = "crm.lead" _description = "Lead/Opportunity" _order = "priority,date_action,id desc" - _inherit = ['mail.thread', 'ir.needaction_mixin'] + _inherit = ['mail.thread', 'ir.needaction_mixin', 'crm.tracking.mixin'] _track = { 'stage_id': { @@ -214,9 +215,6 @@ class crm_lead(format_address, osv.osv): 'write_date': fields.datetime('Update Date', readonly=True), 'categ_ids': fields.many2many('crm.case.categ', 'crm_lead_category_rel', 'lead_id', 'category_id', 'Tags', \ domain="['|', ('section_id', '=', section_id), ('section_id', '=', False), ('object_id.model', '=', 'crm.lead')]", help="Classify and analyze your lead/opportunity categories like: Training, Service"), - 'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \ - domain="['|',('section_id','=',section_id),('section_id','=',False)]", help="From which campaign (seminar, marketing campaign, mass mailing, ...) did this contact come from?"), - 'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel (mail, direct, phone, ...)"), 'contact_name': fields.char('Contact Name', size=64), 'partner_name': fields.char("Customer Name", size=64,help='The name of the future partner company that will be created while converting the lead into opportunity', select=1), 'opt_out': fields.boolean('Opt-Out', oldname='optout', diff --git a/addons/crm/crm_lead_data.xml b/addons/crm/crm_lead_data.xml index 9ea859937a2..4d5e1982306 100644 --- a/addons/crm/crm_lead_data.xml +++ b/addons/crm/crm_lead_data.xml @@ -68,39 +68,6 @@ (4, ref('stage_lead7'))]"/> - - - Telesales - - - - Email Campaign - Services - - - - Email Campaign - Products - - - - Twitter Ads - - - - Google Adwords - - - - Banner Ads - - - - Television - - - - Newsletter - - diff --git a/addons/crm/crm_lead_demo.xml b/addons/crm/crm_lead_demo.xml index 8acefd5c610..f5b38f8078b 100644 --- a/addons/crm/crm_lead_demo.xml +++ b/addons/crm/crm_lead_demo.xml @@ -16,9 +16,7 @@ Paris 93190 +33 1 25 54 45 69 - - 1 @@ -51,9 +49,7 @@ Could you please send me the details ? Purchase Manager Bordeaux - - 1 @@ -80,9 +76,7 @@ Could you please send me the details ? New-York - - 2 @@ -101,9 +95,7 @@ Could you please send me the details ? Drury - - 2 @@ -126,9 +118,7 @@ Could you please send me the details ? Franklinville NJ 08322 +1 813 494 5005 - - 2 @@ -153,9 +143,7 @@ Contact: +1 813 494 5005 Consultant Roma - - 2 @@ -171,9 +159,7 @@ Contact: +1 813 494 5005 Journalist Kiev - - 0 @@ -190,9 +176,7 @@ Contact: +1 813 494 5005 Sales Cambridge - - 1 @@ -209,9 +193,7 @@ Contact: +1 813 494 5005 Sales Oxford - - 2 @@ -227,9 +209,7 @@ Contact: +1 813 494 5005 Medical illustrator Sevilla - - 2 @@ -250,9 +230,7 @@ Andrew Functional Consultant - - 2 @@ -268,9 +246,7 @@ Andrew Shenzhen - - 2 @@ -303,9 +279,7 @@ Andrew 1 rue Rockfeller Melbourne - - 1 @@ -328,9 +302,7 @@ Andrew Wavre 1300 - - 2 @@ -350,7 +322,6 @@ Andrew Wavre virginie@agrolait.com - 1 @@ -377,7 +348,6 @@ Andrew Liverpool L25 4RL - 2 @@ -401,9 +371,7 @@ Andrew Rue des Palais 51, bte 33 Brussels - - 1 @@ -429,7 +397,6 @@ Andrew 3661 Station Street - 1 @@ -456,7 +423,6 @@ Andrew Liverpool L25 4RL - 1 @@ -476,7 +442,6 @@ Andrew Epic Technologies john.b@tech.info - 2 @@ -489,9 +454,7 @@ Andrew “Resource Planning” project develpment - - 2 @@ -507,9 +470,7 @@ Andrew Benjamin Flores Nebula Business - - 1 @@ -522,9 +483,7 @@ Andrew - - 0 @@ -540,9 +499,7 @@ Andrew info@mycompany.net Lima - - 0 @@ -564,7 +521,6 @@ Andrew Buenos Aires B7313 - 2 Conf call with technical service @@ -585,9 +541,7 @@ Andrew Wavre 1300 - - 2 diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index 62c5e14deed..3f10dcf2099 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -230,9 +230,9 @@ - + - + @@ -513,8 +513,9 @@ - - + + + diff --git a/addons/crm/crm_view.xml b/addons/crm/crm_view.xml index 167fd26efc3..b0e64042af7 100644 --- a/addons/crm/crm_view.xml +++ b/addons/crm/crm_view.xml @@ -26,10 +26,10 @@ - - - crm.case.channel.tree - crm.case.channel + + + crm.tracking.medium.tree + crm.tracking.medium @@ -37,9 +37,9 @@ - - crm.case.channel.form - crm.case.channel + + crm.tracking.medium.form + crm.tracking.medium
@@ -49,26 +49,66 @@
- + Channels - crm.case.channel + crm.tracking.medium form tree,form

Click to define a new channel.

- Use channels to track the soure of your leads and opportunities. Channels + Use channels to track the type of source of your leads and opportunities. Channels are mostly used in reporting to analyse sales performance related to marketing efforts.

Some examples of channels: company website, phone call - campaign, reseller, etc. + campaign, reseller, banner, etc.

- + + + crm.tracking.source.tree + crm.tracking.source + + + + + + + + crm.tracking.source.form + crm.tracking.source + +
+ + + +
+
+
+ + Source + crm.tracking.source + form + tree,form + +

+ Click to define a new source. +

+ Use channels to track the source of your leads and opportunities. Channels + are mostly used in reporting to analyse sales performance + related to marketing efforts. +

+ Some examples of channels: partner's website, google.com, + email list name, etc. +

+
+
+ + @@ -155,9 +195,9 @@ - - crm.case.resource.type.tree - crm.case.resource.type + + crm.tracking.campaign.tree + crm.tracking.campaign @@ -168,9 +208,9 @@ - - crm.case.resource.type.form - crm.case.resource.type + + crm.tracking.campaign.form + crm.tracking.campaign
@@ -183,15 +223,15 @@ - + Campaigns - crm.case.resource.type + crm.tracking.campaign form - + - diff --git a/addons/crm/ir_http.py b/addons/crm/ir_http.py new file mode 100644 index 00000000000..e2bc9be4867 --- /dev/null +++ b/addons/crm/ir_http.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +from openerp.http import request +from openerp.osv import orm + + +class ir_http(orm.AbstractModel): + _inherit = 'ir.http' + + def _dispatch(self): + for var, dummy in self.pool['crm.tracking.mixin'].tracking_fields(): + if var in request.params and (var not in request.session or request.session[var] != request.params[var]): + request.session[var] = request.params[var] + return super(ir_http, self)._dispatch() diff --git a/addons/crm/report/crm_lead_report.py b/addons/crm/report/crm_lead_report.py index 7cb355c74d4..32851a753bc 100644 --- a/addons/crm/report/crm_lead_report.py +++ b/addons/crm/report/crm_lead_report.py @@ -44,6 +44,7 @@ class crm_lead_report(osv.osv): _auto = False _description = "CRM Lead Analysis" _rec_name = 'date_deadline' + _inherit = ["crm.tracking.mixin"] _columns = { 'date_deadline': fields.date('Exp. Closing', readonly=True, help="Expected Closing"), @@ -58,10 +59,8 @@ class crm_lead_report(osv.osv): 'delay_expected': fields.float('Overpassed Deadline',digits=(16,2),readonly=True, group_operator="avg"), 'user_id':fields.many2one('res.users', 'User', readonly=True), + 'section_id':fields.many2one('crm.case.section', 'Sales Team', readonly=True), 'country_id':fields.many2one('res.country', 'Country', readonly=True), - 'section_id':fields.many2one('crm.case.section', 'Sales Team', readonly=True), - 'channel_id':fields.many2one('crm.case.channel', 'Channel', readonly=True), - 'type_id':fields.many2one('crm.case.resource.type', 'Campaign', readonly=True), 'company_id': fields.many2one('res.company', 'Company', readonly=True), 'probability': fields.float('Probability',digits=(16,2),readonly=True, group_operator="avg"), 'planned_revenue': fields.float('Planned Revenue',digits=(16,2),readonly=True), @@ -101,8 +100,9 @@ class crm_lead_report(osv.osv): c.company_id, c.priority, c.section_id, - c.channel_id, - c.type_id, + c.campaign_id, + c.source_id, + c.medium_id, c.partner_id, c.country_id, c.planned_revenue, diff --git a/addons/crm/report/crm_lead_report_view.xml b/addons/crm/report/crm_lead_report_view.xml index 74564d8df92..8882b8a1f81 100644 --- a/addons/crm/report/crm_lead_report_view.xml +++ b/addons/crm/report/crm_lead_report_view.xml @@ -74,13 +74,15 @@ - - + + + - - + + + diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv index 3c6fc05a70d..abfe3ebc903 100644 --- a/addons/crm/security/ir.model.access.csv +++ b/addons/crm/security/ir.model.access.csv @@ -3,8 +3,10 @@ access_crm_segmentation_user,crm.segmentation user,model_crm_segmentation,base.g access_crm_segmentation_line_user,crm.segmentation.line user,model_crm_segmentation_line,base.group_sale_salesman,1,0,0,0 access_crm_segmentation,crm.segmentation,model_crm_segmentation,base.group_sale_manager,1,1,1,1 access_crm_segmentation_line,crm.segmentation.line,model_crm_segmentation_line,base.group_sale_manager,1,1,1,1 -access_crm_case_channel_user,crm.case.channel user,model_crm_case_channel,base.group_sale_salesman,1,0,0,0 -access_crm_case_channel_manager,crm.case.channel manager,model_crm_case_channel,base.group_sale_manager,1,1,1,1 +access_crm_tracking_medium_user,crm.tracking.medium user,model_crm_tracking_medium,base.group_sale_salesman,1,0,0,0 +access_crm_tracking_medium_manager,crm.tracking.medium manager,model_crm_tracking_medium,base.group_sale_manager,1,1,1,1 +access_crm_tracking_source_user,crm.tracking.source user,model_crm_tracking_source,base.group_sale_salesman,1,0,0,0 +access_crm_tracking_source_manager,crm.tracking.source manager,model_crm_tracking_source,base.group_sale_manager,1,1,1,1 access_crm_case_categ,crm.case.categ,model_crm_case_categ,base.group_sale_salesman,1,1,1,0 access_crm_lead_manager,crm.lead.manager,model_crm_lead,base.group_sale_manager,1,1,1,1 access_crm_phonecall_manager,crm.phonecall.manager,model_crm_phonecall,base.group_sale_manager,1,1,1,1 @@ -13,8 +15,8 @@ access_crm_lead,crm.lead,model_crm_lead,base.group_sale_salesman,1,1,1,0 access_crm_phonecall,crm.phonecall,model_crm_phonecall,base.group_sale_salesman,1,1,1,0 access_crm_case_stage,crm.case.stage,model_crm_case_stage,,1,0,0,0 access_crm_case_stage_manager,crm.case.stage,model_crm_case_stage,base.group_sale_manager,1,1,1,1 -access_crm_case_resource_type_user,crm_case_resource_type user,model_crm_case_resource_type,base.group_sale_salesman,1,1,1,0 -access_crm_case_resource_type_manager,crm_case_resource_type manager,model_crm_case_resource_type,base.group_sale_manager,1,1,1,1 +access_crm_tracking_campaign_user,crm_tracking_campaign user,model_crm_tracking_campaign,base.group_sale_salesman,1,1,1,0 +access_crm_tracking_campaign_manager,crm_tracking_campaign manager,model_crm_tracking_campaign,base.group_sale_manager,1,1,1,1 access_crm_phonecall_report_user,crm.phonecall.report.user,model_crm_phonecall_report,base.group_sale_salesman,1,0,0,0 access_crm_phonecall_report_manager,crm.phonecall.report,model_crm_phonecall_report,base.group_sale_manager,1,1,1,1 access_res_partner_manager,res.partner.crm.manager,base.model_res_partner,base.group_sale_manager,1,0,0,0 @@ -29,4 +31,4 @@ access_res_partner_bank_type_crm_user,res.partner.bank.type.crm.user,base.model_ access_crm_lead_partner_manager,crm.lead.partner.manager,model_crm_lead,base.group_partner_manager,1,0,0,0 access_crm_phonecall_partner_manager,crm.phonecall.partner.manager,model_crm_phonecall,base.group_partner_manager,1,1,1,1 access_crm_payment_mode_user,crm.payment.mode,model_crm_payment_mode,base.group_sale_salesman,1,0,0,0 -access_crm_payment_mode,crm.payment.mode,model_crm_payment_mode,base.group_sale_manager,1,1,1,1 +access_crm_payment_mode,crm.payment.mode,model_crm_payment_mode,base.group_sale_manager,1,1,1,1 \ No newline at end of file diff --git a/addons/crm_claim/crm_claim_data.xml b/addons/crm_claim/crm_claim_data.xml index 86cf988ff45..58ed29f0a0a 100644 --- a/addons/crm_claim/crm_claim_data.xml +++ b/addons/crm_claim/crm_claim_data.xml @@ -28,12 +28,12 @@ Case Category2 --> - + Corrective - + Preventive diff --git a/addons/crm_helpdesk/crm_helpdesk.py b/addons/crm_helpdesk/crm_helpdesk.py index 0fdcfeb4210..3800efd7cf4 100644 --- a/addons/crm_helpdesk/crm_helpdesk.py +++ b/addons/crm_helpdesk/crm_helpdesk.py @@ -56,7 +56,7 @@ class crm_helpdesk(osv.osv): 'date': fields.datetime('Date'), 'ref': fields.reference('Reference', selection=openerp.addons.base.res.res_request.referencable_models), 'ref2': fields.reference('Reference 2', selection=openerp.addons.base.res.res_request.referencable_models), - 'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel."), + 'channel_id': fields.many2one('crm.tracking.medium', 'Channel', help="Communication channel."), 'planned_revenue': fields.float('Planned Revenue'), 'planned_cost': fields.float('Planned Costs'), 'priority': fields.selection([('0','Low'), ('1','Normal'), ('2','High')], 'Priority'), diff --git a/addons/crm_helpdesk/report/crm_helpdesk_report.py b/addons/crm_helpdesk/report/crm_helpdesk_report.py index 1163db948d2..f7180ef0908 100644 --- a/addons/crm_helpdesk/report/crm_helpdesk_report.py +++ b/addons/crm_helpdesk/report/crm_helpdesk_report.py @@ -56,7 +56,7 @@ class crm_helpdesk_report(osv.osv): 'date_deadline': fields.date('Deadline', select=True), 'priority': fields.selection([('5', 'Lowest'), ('4', 'Low'), \ ('3', 'Normal'), ('2', 'High'), ('1', 'Highest')], 'Priority'), - 'channel_id': fields.many2one('crm.case.channel', 'Channel'), + 'channel_id': fields.many2one('crm.tracking.medium', 'Channel'), 'categ_id': fields.many2one('crm.case.categ', 'Category', \ domain="[('section_id','=',section_id),\ ('object_id.model', '=', 'crm.helpdesk')]"), diff --git a/addons/crm_mass_mailing/__init__.py b/addons/crm_mass_mailing/__init__.py new file mode 100644 index 00000000000..983faeb18a6 --- /dev/null +++ b/addons/crm_mass_mailing/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2014-Today OpenERP SA (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import mass_mailing diff --git a/addons/crm_mass_mailing/__openerp__.py b/addons/crm_mass_mailing/__openerp__.py new file mode 100644 index 00000000000..5e2e320f9f0 --- /dev/null +++ b/addons/crm_mass_mailing/__openerp__.py @@ -0,0 +1,18 @@ +{ + 'name': 'Campaign in Mass Mailing', + 'version': '1.0', + 'summary': 'This module allow to specify a campaign, a source and a channel for a mass mailing campaign.', + 'author': 'OpenERP SA', + 'description': """ +Mass Mailing with Crm Marketing +================================ + +Link module mass mailing with the marketing mixin from crm. + """, + 'depends': ['crm', 'mass_mailing'], + 'data': [ + 'mass_mailing.xml', + ], + 'installable': True, + 'auto_install': True, +} diff --git a/addons/crm_mass_mailing/mass_mailing.py b/addons/crm_mass_mailing/mass_mailing.py new file mode 100644 index 00000000000..6a2e1322136 --- /dev/null +++ b/addons/crm_mass_mailing/mass_mailing.py @@ -0,0 +1,6 @@ +from openerp.osv import osv + + +class MassMailing(osv.Model): + _name = 'mail.mass_mailing' + _inherit = ['mail.mass_mailing', 'crm.tracking.mixin'] diff --git a/addons/crm_mass_mailing/mass_mailing.xml b/addons/crm_mass_mailing/mass_mailing.xml new file mode 100644 index 00000000000..a6768e7be84 --- /dev/null +++ b/addons/crm_mass_mailing/mass_mailing.xml @@ -0,0 +1,23 @@ + + + + + mail.mass_mailing.inherit.form + mail.mass_mailing + + + + + + + + + + + + + + + + + diff --git a/addons/crm_mass_mailing/mass_mailing_data.xml b/addons/crm_mass_mailing/mass_mailing_data.xml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/addons/crm_partner_assign/crm_lead_demo.xml b/addons/crm_partner_assign/crm_lead_demo.xml index a6373ff2f8a..a9da6701c8b 100644 --- a/addons/crm_partner_assign/crm_lead_demo.xml +++ b/addons/crm_partner_assign/crm_lead_demo.xml @@ -12,9 +12,7 @@ Reseller Edinburgh - - 2 diff --git a/addons/crm_partner_assign/crm_portal_view.xml b/addons/crm_partner_assign/crm_portal_view.xml index c3334cfb9ee..0efeaaa2ae7 100644 --- a/addons/crm_partner_assign/crm_portal_view.xml +++ b/addons/crm_partner_assign/crm_portal_view.xml @@ -15,9 +15,9 @@ - + - +