diff options
26 files changed, 225 insertions, 62 deletions
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 275d1fdf80a..e46b3e5f535 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -1037,10 +1037,13 @@ class AccountMoveLine(models.Model): # the provided values were not already multi-currency if account.currency_id and 'amount_currency' not in vals and account.currency_id.id != account.company_id.currency_id.id: vals['currency_id'] = account.currency_id.id - ctx = {} - if 'date' in vals: - ctx['date'] = vals['date'] - vals['amount_currency'] = account.company_id.currency_id.with_context(ctx).compute(amount, account.currency_id) + if self._context.get('skip_full_reconcile_check') == 'amount_currency_excluded': + vals['amount_currency'] = 0.0 + else: + ctx = {} + if 'date' in vals: + ctx['date'] = vals['date'] + vals['amount_currency'] = account.company_id.currency_id.with_context(ctx).compute(amount, account.currency_id) if not ok: raise UserError(_('You cannot use this general account in this journal, check the tab \'Entry Controls\' on the related journal.')) diff --git a/addons/account_asset/i18n/account_asset.pot b/addons/account_asset/i18n/account_asset.pot index 02b112eb313..ee75e4244e6 100644 --- a/addons/account_asset/i18n/account_asset.pot +++ b/addons/account_asset/i18n/account_asset.pot @@ -455,6 +455,7 @@ msgstr "" #. module: account_asset #: model:ir.model.fields,field_description:account_asset.field_account_asset_category_account_depreciation_id +#: model:ir.ui.view,arch_db:account_asset.view_account_asset_asset_form msgid "Depreciation Entries: Asset Account" msgstr "" diff --git a/addons/account_asset/models/account_asset.py b/addons/account_asset/models/account_asset.py index 7c5585b174b..fff246a112c 100644 --- a/addons/account_asset/models/account_asset.py +++ b/addons/account_asset/models/account_asset.py @@ -378,7 +378,7 @@ class AccountAssetAsset(models.Model): @api.multi def _compute_entries(self, date, group_entries=False): - depreciation_ids = self.env['account.asset.depreciation.line'].with_context(depreciation_date=date).search([ + depreciation_ids = self.env['account.asset.depreciation.line'].search([ ('asset_id', 'in', self.ids), ('depreciation_date', '<=', date), ('move_check', '=', False)]) if group_entries: diff --git a/addons/barcodes/static/src/js/form_view_barcode_handler.js b/addons/barcodes/static/src/js/form_view_barcode_handler.js index b00bce51c5e..ffc6dfc7383 100644 --- a/addons/barcodes/static/src/js/form_view_barcode_handler.js +++ b/addons/barcodes/static/src/js/form_view_barcode_handler.js @@ -81,7 +81,7 @@ var FormViewBarcodeHandler = common.AbstractField.extend(BarcodeHandlerMixin, { if (this.last_scanned_barcode) { var new_qty = window.prompt(_t('Set quantity'), character) || "0"; new_qty = new_qty.replace(',', '.'); - var record = this._get_records(field).find(function(record) { + var record = _.find(this._get_records(field), function (record) { return record.get('product_barcode') === self.last_scanned_barcode; }); if (record) { @@ -159,8 +159,13 @@ var FormViewBarcodeHandler = common.AbstractField.extend(BarcodeHandlerMixin, { }, _get_records: function(field) { - return field.viewmanager.active_view.controller.records || // tree view - field.viewmanager.active_view.controller.widgets; // kanban view + var active_view = field.viewmanager.active_view; + if (active_view.type === "kanban") { + return active_view.controller.widgets; + } else { + // tree view case + return active_view.controller.records.records; + } }, }); diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml index b3e54885818..6565ec972f5 100644 --- a/addons/crm/views/crm_lead_views.xml +++ b/addons/crm/views/crm_lead_views.xml @@ -525,7 +525,7 @@ <field name="source_id" invisible="1"/> <field name="stage_id"/> <field name="planned_revenue" sum="Expected Revenues"/> - <field name="probability" /> + <field name="probability" avg="Avg. of Probability"/> <field name="team_id"/> <field name="user_id"/> <field name="referred" invisible="1"/> diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index f8dc372f39e..fa4c6498366 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -136,6 +136,7 @@ class HrExpense(models.Model): 'product_id': line.get('product_id'), 'product_uom_id': line.get('uom_id'), 'analytic_account_id': line.get('analytic_account_id'), + 'payment_id': line.get('payment_id'), } @api.multi @@ -185,7 +186,10 @@ class HrExpense(models.Model): 'journal_id': journal.id, 'company_id': self.env.user.company_id.id, 'date': acc_date, - 'ref': ref + 'ref': ref, + # force the name to the default value, to avoid an eventual 'default_name' in the context + # to set it to '' which cause no number to be given to the account.move when posted. + 'name': '/', }) for expense in expense_list: company_currency = expense.company_id.currency_id @@ -194,11 +198,29 @@ class HrExpense(models.Model): move_lines = expense._move_line_get() #create one more move line, a counterline for the total on payable account + payment_id = False total, total_currency, move_lines = expense._compute_expense_totals(company_currency, move_lines, acc_date) if expense.payment_mode == 'company_account': if not expense.sheet_id.bank_journal_id.default_credit_account_id: raise UserError(_("No credit account found for the %s journal, please configure one.") % (expense.sheet_id.bank_journal_id.name)) emp_account = expense.sheet_id.bank_journal_id.default_credit_account_id.id + journal = expense.bank_journal_id + #create payment + payment_methods = (total < 0) and journal.outbound_payment_method_ids or journal.inbound_payment_method_ids + journal_currency = journal.currency_id or journal.company_id.currency_id + payment = self.env['account.payment'].create({ + 'payment_method_id': payment_methods and payment_methods[0].id or False, + 'payment_type': total < 0 and 'outbound' or 'inbound', + 'partner_id': expense.employee_id.address_home_id.commercial_partner_id.id, + 'partner_type': 'supplier', + 'journal_id': journal.id, + 'payment_date': expense.date, + 'state': 'reconciled', + 'currency_id': diff_currency_p and expense.currency_id.id or journal_currency.id, + 'amount': diff_currency_p and abs(total_currency) or abs(total), + 'name': expense.name, + }) + payment_id = payment.id else: if not expense.employee_id.address_home_id: raise UserError(_("No Home Address found for the employee %s, please configure one.") % (expense.employee_id.name)) @@ -212,6 +234,7 @@ class HrExpense(models.Model): 'date_maturity': acc_date, 'amount_currency': diff_currency_p and total_currency or False, 'currency_id': diff_currency_p and expense.currency_id.id or False, + 'payment_id': payment_id, }) #convert eml into an osv-valid format diff --git a/addons/l10n_in/data/account_tax_template_data.xml b/addons/l10n_in/data/account_tax_template_data.xml index 43251bc2ad8..85fb8c4c6d6 100644 --- a/addons/l10n_in/data/account_tax_template_data.xml +++ b/addons/l10n_in/data/account_tax_template_data.xml @@ -462,7 +462,7 @@ <field name="account_id" ref="p11235"/> <field name="refund_account_id" ref="p20011"/> <field name="chart_template_id" ref="indian_chart_template_standard"/> - <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_70'), ref('st_tag_in_21')])]"/> + <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_21'), ref('st_tag_in_66')])]"/> </record> <record id="input_tax_12" model="account.tax.template"> <field name="name">(standard)Input Sales Tax @ 12%</field> @@ -474,7 +474,7 @@ <field name="account_id" ref="p11235"/> <field name="refund_account_id" ref="p20011"/> <field name="chart_template_id" ref="indian_chart_template_standard"/> - <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_56')])]"/> + <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_18'), ref('st_tag_in_63')])]"/> </record> <record id="input_tax_4" model="account.tax.template"> <field name="name">(standard)Input Sales Tax @ 4%</field> @@ -486,7 +486,7 @@ <field name="account_id" ref="p11235"/> <field name="refund_account_id" ref="p20011"/> <field name="chart_template_id" ref="indian_chart_template_standard"/> - <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_15'), ref('st_tag_in_70')])]"/> + <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_15'), ref('st_tag_in_60')])]"/> </record> <record id="add_input_vat_1" model="account.tax.template"> <field name="name">(standard)Additional Input VAT @ 1%</field> @@ -605,7 +605,7 @@ <field name="account_id" ref="p112341"/> <field name="refund_account_id" ref="p20011"/> <field name="chart_template_id" ref="indian_chart_template_standard"/> - <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_25')])]"/> + <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_25'), ref('st_tag_in_63')])]"/> </record> <record id="sale_edu_cess_service_tax_2" model="account.tax.template"> <field name="name">(standard)Input Education Cess @ 2% On Service Tax</field> diff --git a/addons/purchase/models/account_invoice.py b/addons/purchase/models/account_invoice.py index 38cf5949e04..dd351d2d564 100644 --- a/addons/purchase/models/account_invoice.py +++ b/addons/purchase/models/account_invoice.py @@ -30,7 +30,6 @@ class AccountInvoice(models.Model): ]} return result - def _prepare_invoice_line_from_po_line(self, line): if line.product_id.purchase_method == 'purchase': qty = line.product_qty - line.qty_invoiced @@ -43,7 +42,7 @@ class AccountInvoice(models.Model): invoice_line = self.env['account.invoice.line'] data = { 'purchase_line_id': line.id, - 'name': line.name, + 'name': self.purchase_id.name+': '+line.name, 'origin': self.purchase_id.origin, 'uom_id': line.product_uom.id, 'product_id': line.product_id.id, @@ -52,6 +51,7 @@ class AccountInvoice(models.Model): 'quantity': qty, 'discount': 0.0, 'account_analytic_id': line.account_analytic_id.id, + 'analytic_tag_ids': line.analytic_tag_ids.ids, 'invoice_line_tax_ids': invoice_line_tax_ids.ids } account = invoice_line.get_invoice_line_account('in_invoice', line.product_id, self.purchase_id.fiscal_position_id, self.env.user.company_id) @@ -73,9 +73,6 @@ class AccountInvoice(models.Model): if line in self.invoice_line_ids.mapped('purchase_line_id'): continue data = self._prepare_invoice_line_from_po_line(line) - account = new_lines.get_invoice_line_account('in_invoice', line.product_id, self.purchase_id.fiscal_position_id, self.env.user.company_id) - if account: - data['account_id'] = account.id new_line = new_lines.new(data) new_line._set_additional_fields(self) new_lines += new_line diff --git a/addons/rating_project/models/project.py b/addons/rating_project/models/project.py index a82ca8cc1b9..345576768ae 100644 --- a/addons/rating_project/models/project.py +++ b/addons/rating_project/models/project.py @@ -10,10 +10,13 @@ class ProjectTaskType(models.Model): _inherit = 'project.task.type' + def _default_domain_rating_template_id(self): + return [('model', '=', 'project.task')] + rating_template_id = fields.Many2one( 'mail.template', string='Rating Email Template', - domain=[('model', '=', 'project.task')], + domain=lambda self: self._default_domain_rating_template_id(), help="Select an email template. An email will be sent to the customer when the task reach this step.") auto_validation_kanban_state = fields.Boolean('Automatic kanban status', default=False, help="Automatically modify the kanban state when the customer reply to the feedback for this stage.\n" diff --git a/addons/rating_project_issue/models/project_issue.py b/addons/rating_project_issue/models/project_issue.py index f771f328f84..5b2b126cad2 100644 --- a/addons/rating_project_issue/models/project_issue.py +++ b/addons/rating_project_issue/models/project_issue.py @@ -28,6 +28,16 @@ class ProjectIssue(models.Model): return super(ProjectIssue, self).rating_apply(rate, token=token, feedback=feedback, subtype="rating_project_issue.mt_issue_rating") + +class Stage(models.Model): + + _inherit = ['project.task.type'] + + def _default_domain_rating_template_id(self): + domain = super(Stage, self)._default_domain_rating_template_id() + return ['|'] + domain + [('model', '=', 'project.issue')] + + class Project(models.Model): _inherit = "project.project" diff --git a/addons/sale/wizard/sale_make_invoice_advance.py b/addons/sale/wizard/sale_make_invoice_advance.py index 838f9abde22..b780a526c36 100644 --- a/addons/sale/wizard/sale_make_invoice_advance.py +++ b/addons/sale/wizard/sale_make_invoice_advance.py @@ -83,6 +83,10 @@ class SaleAdvancePaymentInv(models.TransientModel): else: amount = self.amount name = _('Down Payment') + if order.fiscal_position_id and self.product_id.taxes_id: + tax_ids = order.fiscal_position_id.map_tax(self.product_id.taxes_id).ids + else: + tax_ids = self.product_id.taxes_id.ids invoice = inv_obj.create({ 'name': order.client_order_ref or order.name, @@ -102,7 +106,7 @@ class SaleAdvancePaymentInv(models.TransientModel): 'uom_id': self.product_id.uom_id.id, 'product_id': self.product_id.id, 'sale_line_ids': [(6, 0, [so_line.id])], - 'invoice_line_tax_ids': [(6, 0, [x.id for x in self.product_id.taxes_id])], + 'invoice_line_tax_ids': [(6, 0, tax_ids)], 'account_analytic_id': order.project_id.id or False, })], 'currency_id': order.pricelist_id.currency_id.id, @@ -142,6 +146,10 @@ class SaleAdvancePaymentInv(models.TransientModel): raise UserError(_('The product used to invoice a down payment should have an invoice policy set to "Ordered quantities". Please update your deposit product to be able to create a deposit invoice.')) if self.product_id.type != 'service': raise UserError(_("The product used to invoice a down payment should be of type 'Service'. Please use another product or update this product.")) + if order.fiscal_position_id and self.product_id.taxes_id: + tax_ids = order.fiscal_position_id.map_tax(self.product_id.taxes_id).ids + else: + tax_ids = self.product_id.taxes_id.ids so_line = sale_line_obj.create({ 'name': _('Advance: %s') % (time.strftime('%m %Y'),), 'price_unit': amount, @@ -150,7 +158,7 @@ class SaleAdvancePaymentInv(models.TransientModel): 'discount': 0.0, 'product_uom': self.product_id.uom_id.id, 'product_id': self.product_id.id, - 'tax_id': [(6, 0, self.product_id.taxes_id.ids)], + 'tax_id': [(6, 0, tax_ids)], }) self._create_invoice(order, so_line, amount) if self._context.get('open_invoices', False): diff --git a/addons/stock/models/stock_inventory.py b/addons/stock/models/stock_inventory.py index 228e921c259..47f080a1109 100644 --- a/addons/stock/models/stock_inventory.py +++ b/addons/stock/models/stock_inventory.py @@ -91,14 +91,12 @@ class Inventory(models.Model): ('category', _('One product category')), ('product', _('One product only')), ('partial', _('Select products manually'))] - stock_settings = self.env['stock.config.settings'].search([], limit=1, order='id DESC') - if not stock_settings: - return res_filter - if stock_settings.group_stock_tracking_owner: + + if self.user_has_groups('stock.group_tracking_owner'): res_filter += [('owner', _('One owner only')), ('product_owner', _('One product for a specific owner'))] - if stock_settings.group_stock_production_lot: + if self.user_has_groups('stock.group_production_lot'): res_filter.append(('lot', _('One Lot/Serial Number'))) - if stock_settings.group_stock_tracking_lot: + if self.user_has_groups('stock.group_tracking_lot'): res_filter.append(('pack', _('A Pack'))) return res_filter diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 7cb80c8b360..0ddd9de24d2 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -194,7 +194,6 @@ class StockMove(models.Model): @api.multi def _compute_string_qty_information(self): - StockConfig = self.env['stock.config.settings'] precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') void_moves = self.filtered(lambda move: move.state in ('draft', 'done', 'cancel') or move.location_id.usage != 'internal') other_moves = self - void_moves @@ -205,9 +204,7 @@ class StockMove(models.Model): total_available = move.product_id.uom_id._compute_quantity(total_available, move.product_uom, round=False) total_available = float_round(total_available, precision_digits=precision) info = str(total_available) - # look in the settings if we need to display the UoM name or not - config = StockConfig.search([], limit=1, order='id DESC') - if config and config.group_uom: + if self.user_has_groups('product.group_uom'): info += ' ' + move.product_uom.name if move.reserved_availability: if move.reserved_availability != total_available: @@ -380,7 +377,7 @@ class StockMove(models.Model): # if no specialized push rule has been found yet, we try to find a general one (without route) rules = Push.search(domain + [('route_id', '=', False)], order='sequence') # Make sure it is not returning the return - if rules and (not move.origin_returned_move_id or move.origin_returned_move_id.location_id.id != rules.location_dest_id.id): + if rules and (not move.origin_returned_move_id or move.origin_returned_move_id.location_dest_id.id != rules.location_dest_id.id): rules._apply(move) return True diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py index 4cf59568085..634c3cfbdd0 100644 --- a/addons/stock_account/models/stock.py +++ b/addons/stock_account/models/stock.py @@ -115,6 +115,11 @@ class StockQuant(models.Model): else: self.with_context(force_company=company_from.id)._create_account_move_line(move, acc_valuation, acc_dest, journal_id) + if move.company_id.anglo_saxon_accounting and move.location_id.usage == 'supplier' and move.location_dest_id.usage == 'customer': + # Creates an account entry from stock_input to stock_output on a dropship move. https://github.com/odoo/odoo/issues/12687 + journal_id, acc_src, acc_dest, acc_valuation = move._get_accounting_data_for_valuation() + self.with_context(force_company=move.company_id.id)._create_account_move_line(move, acc_src, acc_dest, journal_id) + def _create_account_move_line(self, move, credit_account_id, debit_account_id, journal_id): # group quants by cost quant_cost_qty = defaultdict(lambda: 0.0) diff --git a/addons/web/static/src/js/view_manager.js b/addons/web/static/src/js/view_manager.js index 4255f163068..e0d0135185f 100644 --- a/addons/web/static/src/js/view_manager.js +++ b/addons/web/static/src/js/view_manager.js @@ -459,6 +459,9 @@ var ViewManager = Widget.extend(ControlPanelMixin, { } }, do_load_state: function(state, warm) { + if (state.view_type && state.view_type !== this.active_view.type) { + this.switch_mode(state.view_type, true); + } this.active_view.controller.do_load_state(state, warm); }, }); diff --git a/addons/web/static/src/js/views/list_view.js b/addons/web/static/src/js/views/list_view.js index f2e4f565a79..9382b255b01 100644 --- a/addons/web/static/src/js/views/list_view.js +++ b/addons/web/static/src/js/views/list_view.js @@ -1733,7 +1733,7 @@ var Column = Class.extend({ } var aggregation_func = (this.sum && 'sum') || (this.avg && 'avg') || - (this.max && 'max') || (this.min && 'min') || this.group_operator; + (this.max && 'max') || (this.min && 'min'); if (!aggregation_func) { return {}; diff --git a/addons/web/static/src/js/web_client.js b/addons/web/static/src/js/web_client.js index d94f686c440..52962ed284b 100644 --- a/addons/web/static/src/js/web_client.js +++ b/addons/web/static/src/js/web_client.js @@ -113,25 +113,37 @@ return AbstractWebClient.extend({ } }, on_hashchange: function(event) { + if (this._ignore_hashchange) { + this._ignore_hashchange = false; + return; + } + var self = this; - var stringstate = event.getState(false); - if (!_.isEqual(this._current_state, stringstate)) { - var state = event.getState(true); - if(!state.action && state.menu_id) { - self.menu.is_bound.done(function() { - self.menu.menu_click(state.menu_id); - }); - } else { - state._push_me = false; // no need to push state back... - this.action_manager.do_load_state(state, !!this._current_state).then(function () { - var action = self.action_manager.get_inner_action(); - if (action) { - self.menu.open_action(action.action_descr.id); - } - }); + this.clear_uncommitted_changes().then(function () { + var stringstate = event.getState(false); + if (!_.isEqual(self._current_state, stringstate)) { + var state = event.getState(true); + if(!state.action && state.menu_id) { + self.menu.is_bound.done(function() { + self.menu.menu_click(state.menu_id); + }); + } else { + state._push_me = false; // no need to push state back... + self.action_manager.do_load_state(state, !!self._current_state).then(function () { + var action = self.action_manager.get_inner_action(); + if (action) { + self.menu.open_action(action.action_descr.id); + } + }); + } } - } - this._current_state = stringstate; + self._current_state = stringstate; + }, function () { + if (event) { + self._ignore_hashchange = true; + window.location = event.originalEvent.oldURL; + } + }); }, on_menu_action: function(options) { var self = this; diff --git a/addons/website_blog/models/website_blog.py b/addons/website_blog/models/website_blog.py index 63911cf92ff..6045070acab 100644 --- a/addons/website_blog/models/website_blog.py +++ b/addons/website_blog/models/website_blog.py @@ -32,6 +32,20 @@ class Blog(models.Model): return res @api.multi + def message_post(self, parent_id=False, subtype=None, **kwargs): + """ Temporary workaround to avoid spam. If someone replies on a channel + through the 'Presentation Published' email, it should be considered as a + note as we don't want all channel followers to be notified of this answer. """ + self.ensure_one() + if parent_id: + parent_message = self.env['mail.message'].sudo().browse(parent_id) + if parent_message.subtype_id and parent_message.subtype_id == self.env.ref('website_blog.mt_blog_blog_published'): + if kwargs.get('subtype_id'): + kwargs['subtype_id'] = False + subtype = 'mail.mt_note' + return super(Blog, self).message_post(parent_id=parent_id, subtype=subtype, **kwargs) + + @api.multi def all_tags(self, min_limit=1): req = """ SELECT diff --git a/addons/website_quote/views/website_quote_templates.xml b/addons/website_quote/views/website_quote_templates.xml index 1bfa7e47a81..463f10b80d4 100644 --- a/addons/website_quote/views/website_quote_templates.xml +++ b/addons/website_quote/views/website_quote_templates.xml @@ -298,7 +298,7 @@ <div class="modal-body" id="sign-dialog"> <p> <span>I agree that by signing this proposal, I - accept it on the behalf of </span><b t-field="quotation.company_id"/><span>, for an amount of </span> + accept it on the behalf of </span><b t-field="quotation.partner_id.commercial_partner_id"/><span>, for an amount of </span> <b data-id="total_amount" t-field="quotation.amount_total" t-options='{"widget": "monetary", "display_currency": quotation.pricelist_id.currency_id}'/> <span>with payment terms: </span><b t-field="quotation.payment_term_id"/>. @@ -325,7 +325,7 @@ <div class="modal-body" id="sign-dialog"> <p> <span>I agree that by paying this proposal, I - accept it on the behalf of </span><b t-field="quotation.company_id"/><span>, for an amount of </span> + accept it on the behalf of </span><b t-field="quotation.partner_id.commercial_partner_id"/><span>, for an amount of </span> <b data-id="total_amount" t-field="quotation.amount_total" t-options='{"widget": "monetary", "display_currency": quotation.pricelist_id.currency_id}'/> <span>with payment terms: </span><b t-field="quotation.payment_term_id"/>. diff --git a/addons/website_sale/models/product.py b/addons/website_sale/models/product.py index 9e53f039407..d5a8f77a72d 100644 --- a/addons/website_sale/models/product.py +++ b/addons/website_sale/models/product.py @@ -207,7 +207,6 @@ class Product(models.Model): self.ensure_one() return self.product_tmpl_id.website_publish_button() - class ProductAttribute(models.Model): _inherit = "product.attribute" diff --git a/addons/website_slides/models/slides.py b/addons/website_slides/models/slides.py index b6dc690cc99..d02da6b19cb 100644 --- a/addons/website_slides/models/slides.py +++ b/addons/website_slides/models/slides.py @@ -164,6 +164,20 @@ class Channel(models.Model): self.with_context(active_test=False).mapped('slide_ids').write({'active': vals['active']}) return res + @api.multi + def message_post(self, parent_id=False, subtype=None, **kwargs): + """ Temporary workaround to avoid spam. If someone replies on a channel + through the 'Presentation Published' email, it should be considered as a + note as we don't want all channel followers to be notified of this answer. """ + self.ensure_one() + if parent_id: + parent_message = self.env['mail.message'].sudo().browse(parent_id) + if parent_message.subtype_id and parent_message.subtype_id == self.env.ref('website_slides.mt_channel_slide_published'): + if kwargs.get('subtype_id'): + kwargs['subtype_id'] = False + subtype = 'mail.mt_note' + return super(Channel, self).message_post(parent_id=parent_id, subtype=subtype, **kwargs) + class Category(models.Model): """ Channel contain various categories to manage its slides """ diff --git a/odoo/addons/test_new_api/tests/__init__.py b/odoo/addons/test_new_api/tests/__init__.py index 2bbcfe40990..b90ccc96825 100644 --- a/odoo/addons/test_new_api/tests/__init__.py +++ b/odoo/addons/test_new_api/tests/__init__.py @@ -3,5 +3,6 @@ from . import test_new_fields from . import test_onchange from . import test_attributes +from . import test_one2many from . import test_ui from . import test_domain diff --git a/odoo/addons/test_new_api/tests/test_onchange.py b/odoo/addons/test_new_api/tests/test_onchange.py index 87fc8229b7c..a6e2bff1b35 100644 --- a/odoo/addons/test_new_api/tests/test_onchange.py +++ b/odoo/addons/test_new_api/tests/test_onchange.py @@ -176,7 +176,7 @@ class TestOnChange(common.TransactionCase): self.assertEqual(values, { 'name': partner.name, 'partner': partner.id, - 'lines': [(5,), (4, line.id)], + 'lines': [(6, 0, [line.id])], }) # modify 'partner' diff --git a/odoo/fields.py b/odoo/fields.py index ee732b5b62f..c542945a781 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -894,10 +894,12 @@ class Field(object): env.invalidate(spec) else: - # simply write to the database, and update cache + # Write to database write_value = self.convert_to_write(self.convert_to_record(value, record), record) record.write({self.name: write_value}) - record._cache[self] = value + # Update the cache unless value contains a new record + if not (self.relational and not all(value)): + record._cache[self] = value ############################################################################ # @@ -1987,7 +1989,7 @@ class _RelationalMulti(_Relational): def convert_to_write(self, value, record): # make result with new and existing records - result = [(5,)] + result = [(6, 0, [])] for record in value: if not record.id: values = {name: record[name] for name in record._cache} @@ -1998,7 +2000,7 @@ class _RelationalMulti(_Relational): values = record._convert_to_write(values) result.append((1, record.id, values)) else: - result.append((4, record.id)) + result[0][2].append(record.id) return result def convert_to_onchange(self, value, record, fnames=()): @@ -2171,7 +2173,11 @@ class One2many(_RelationalMulti): query = "SELECT id FROM %s WHERE %s=%%s AND id <> ALL(%%s)" % (comodel._table, inverse) comodel._cr.execute(query, (record.id, act[2] or [0])) lines = comodel.browse([row[0] for row in comodel._cr.fetchall()]) - lines.write({inverse: False}) + inverse_field = comodel._fields[inverse] + if inverse_field.ondelete == 'cascade': + lines.unlink() + else: + lines.write({inverse: False}) class Many2many(_RelationalMulti): diff --git a/odoo/models.py b/odoo/models.py index 714cde7ff0b..8f15a59d7cd 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -3508,8 +3508,7 @@ class BaseModel(object): ``(6, _, ids)`` replaces all existing records in the set by the ``ids`` list, equivalent to using the command ``5`` followed by a command - ``4`` for each ``id`` in ``ids``. Can not be used on - :class:`~odoo.fields.One2many`. + ``4`` for each ``id`` in ``ids``. .. note:: Values marked as ``_`` in the list above are ignored and can be anything, generally ``0`` or ``False``. diff --git a/openerp/addons/test_new_api/tests/test_one2many.py b/openerp/addons/test_new_api/tests/test_one2many.py new file mode 100644 index 00000000000..38dee13ab54 --- /dev/null +++ b/openerp/addons/test_new_api/tests/test_one2many.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +from openerp.tests.common import TransactionCase + + +class One2manyCase(TransactionCase): + def setUp(self): + super(One2manyCase, self).setUp() + self.Line = self.env["test_new_api.multi.line"] + self.multi = self.env["test_new_api.multi"].create({ + "name": "What is up?" + }) + + def operations(self): + """Run operations on o2m fields to check all works fine.""" + # Check the lines first + self.assertItemsEqual( + self.multi.lines.mapped('name'), + map(str, range(10))) + # Modify the first line and drop the last one + self.multi.lines[0].name = "hello" + self.multi.lines = self.multi.lines[:-1] + self.assertEqual(len(self.multi.lines), 9) + self.assertIn("hello", self.multi.lines.mapped('name')) + # Invalidate the cache and check again; this crashes if the value + # of self.multi.lines in cache contains new records + self.multi.invalidate_cache() + self.assertEqual(len(self.multi.lines), 9) + self.assertIn("hello", self.multi.lines.mapped('name')) + + def test_new_one_by_one(self): + """Check lines created with ``new()`` and appended one by one.""" + for name in range(10): + self.multi.lines |= self.Line.new({"name": str(name)}) + self.operations() + + def test_new_single(self): + """Check lines created with ``new()`` and added in one step.""" + self.multi.lines = self.Line.browse( + [self.Line.new({"name": str(name)}).id for name in range(10)] + ) + self.operations() + + def test_create_one_by_one(self): + """Check lines created with ``create()`` and appended one by one.""" + for name in range(10): + self.multi.lines |= self.Line.create({"name": str(name)}) + self.operations() + + def test_create_single(self): + """Check lines created with ``create()`` and added in one step.""" + self.multi.lines = self.Line.browse( + [self.Line.create({"name": str(name)}).id for name in range(10)] + ) + self.operations() + + def test_rpcstyle_one_by_one(self): + """Check lines created with RPC style and appended one by one.""" + for name in range(10): + self.multi.lines = [(0, 0, {"name": str(name)})] + self.operations() + + def test_rpcstyle_single(self): + """Check lines created with RPC style and added in one step""" + self.multi.lines = [(0, 0, {'name': str(name)}) for name in range(10)] + self.operations() |
