diff --git a/addons/account/models/account.py b/addons/account/models/account.py index 59fd1b93f87..b9d5b680820 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -684,15 +684,13 @@ class AccountTax(models.Model): @api.multi def unlink(self): company_id = self.env.user.company_id.id - ir_values = self.env['ir.values'] - supplier_taxes_id = set(ir_values.get_default('product.template', 'supplier_taxes_id', company_id=company_id) or []) - deleted_sup_tax = self.filtered(lambda tax: tax.id in supplier_taxes_id) - if deleted_sup_tax: - ir_values.sudo().set_default('product.template', "supplier_taxes_id", list(supplier_taxes_id - set(deleted_sup_tax.ids)), for_all_users=True, company_id=company_id) - taxes_id = set(self.env['ir.values'].get_default('product.template', 'taxes_id', company_id=company_id) or []) - deleted_tax = self.filtered(lambda tax: tax.id in taxes_id) - if deleted_tax: - ir_values.sudo().set_default('product.template', "taxes_id", list(taxes_id - set(deleted_tax.ids)), for_all_users=True, company_id=company_id) + IrDefault = self.env['ir.default'] + taxes = self.browse(IrDefault.get('product.template', 'taxes_id', company_id=company_id) or []) + if self & taxes: + IrDefault.sudo().set('product.template', 'taxes_id', (taxes - self).ids, company_id=company_id) + taxes = self.browse(IrDefault.get('product.template', 'supplier_taxes_id', company_id=company_id) or []) + if self & taxes: + IrDefault.sudo().set('product.template', 'supplier_taxes_id', (taxes - self).ids, company_id=company_id) return super(AccountTax, self).unlink() @api.one diff --git a/addons/account/models/account_config_settings.py b/addons/account/models/account_config_settings.py index 9f9ad09a298..2cc8568e049 100644 --- a/addons/account/models/account_config_settings.py +++ b/addons/account/models/account_config_settings.py @@ -82,9 +82,9 @@ class AccountConfigSettings(models.TransientModel): if self.group_multi_currency: self.env.ref('base.group_user').write({'implied_ids': [(4, self.env.ref('product.group_sale_pricelist').id)]}) """ Set the product taxes if they have changed """ - ir_values_obj = self.env['ir.values'] - ir_values_obj.sudo().set_default('product.template', "taxes_id", [self.default_sale_tax_id.id] if self.default_sale_tax_id else False, for_all_users=True, company_id=self.company_id.id) - ir_values_obj.sudo().set_default('product.template', "supplier_taxes_id", [self.default_purchase_tax_id.id] if self.default_purchase_tax_id else False, for_all_users=True, company_id=self.company_id.id) + IrDefault = self.env['ir.default'].sudo() + IrDefault.set('product.template', "taxes_id", self.default_sale_tax_id.ids, company_id=self.company_id.id) + IrDefault.set('product.template', "supplier_taxes_id", self.default_purchase_tax_id.ids, company_id=self.company_id.id) """ install a chart of accounts for the given company (if required) """ if self.chart_template_id and self.chart_template_id != self.company_id.chart_template_id: wizard = self.env['wizard.multi.charts.accounts'].create({ diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py index 6d074310e91..64c414937a1 100644 --- a/addons/account/models/chart_template.py +++ b/addons/account/models/chart_template.py @@ -876,7 +876,6 @@ class WizardMultiChartsAccounts(models.TransientModel): res.unlink() existing_accounts.unlink() - ir_values_obj = self.env['ir.values'] company = self.company_id self.company_id.write({'currency_id': self.currency_id.id, 'accounts_code_digits': self.code_digits, @@ -903,10 +902,11 @@ class WizardMultiChartsAccounts(models.TransientModel): acc_template_ref, taxes_ref = self.chart_template_id._install_template(company, code_digits=self.code_digits, transfer_account_id=self.transfer_account_id) # write values of default taxes for product as super user + IrDefault = self.env['ir.default'] if self.sale_tax_id and taxes_ref: - ir_values_obj.sudo().set_default('product.template', "taxes_id", [taxes_ref[self.sale_tax_id.id]], for_all_users=True, company_id=company.id) + IrDefault.sudo().set('product.template', "taxes_id", [taxes_ref[self.sale_tax_id.id]], company_id=company.id) if self.purchase_tax_id and taxes_ref: - ir_values_obj.sudo().set_default('product.template', "supplier_taxes_id", [taxes_ref[self.purchase_tax_id.id]], for_all_users=True, company_id=company.id) + IrDefault.sudo().set('product.template', "supplier_taxes_id", [taxes_ref[self.purchase_tax_id.id]], company_id=company.id) # Create Bank journals self._create_bank_journals_from_o2m(company, acc_template_ref) diff --git a/addons/http_routing/models/ir_http.py b/addons/http_routing/models/ir_http.py index e429f98c5e5..ad655c15ad3 100644 --- a/addons/http_routing/models/ir_http.py +++ b/addons/http_routing/models/ir_http.py @@ -199,7 +199,7 @@ class IrHttp(models.AbstractModel): @classmethod def _get_default_lang(cls): - lang_code = request.env['ir.values'].sudo().get_default('res.partner', 'lang') + lang_code = request.env['ir.default'].sudo().get('res.partner', 'lang') if lang_code: return request.env['res.lang'].search([('code', '=', lang_code)], limit=1) return request.env['res.lang'].search([], limit=1) diff --git a/addons/l10n_cn_small_business/data/l10n_cn_small_business_chart_data.xml b/addons/l10n_cn_small_business/data/l10n_cn_small_business_chart_data.xml index 370ef50f5dc..e24940ca1f8 100644 --- a/addons/l10n_cn_small_business/data/l10n_cn_small_business_chart_data.xml +++ b/addons/l10n_cn_small_business/data/l10n_cn_small_business_chart_data.xml @@ -540,14 +540,6 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html - - 小企业会计科目表(财会[2011]17号《小企业会计准则》) - account.chart.template - default - - - - 增值税17%销项税 diff --git a/addons/l10n_cn_standard/data/account_chart_template_data.xml b/addons/l10n_cn_standard/data/account_chart_template_data.xml index 4302c9f5c5c..f9c22664671 100644 --- a/addons/l10n_cn_standard/data/account_chart_template_data.xml +++ b/addons/l10n_cn_standard/data/account_chart_template_data.xml @@ -10,12 +10,5 @@ - - 中国会计科目表 (财会[2006]3号《企业会计准则》) - account.chart.template - default - - - diff --git a/addons/payment/controllers/portal.py b/addons/payment/controllers/portal.py index 5b170662f0e..dc402446ea3 100644 --- a/addons/payment/controllers/portal.py +++ b/addons/payment/controllers/portal.py @@ -31,7 +31,7 @@ class WebsitePayment(http.Controller): # Try default one then fallback on first acquirer_id = acquirer_id and int(acquirer_id) or \ - env['ir.values'].get_default('payment.transaction', 'acquirer_id', company_id=user.company_id.id) or \ + env['ir.default'].get('payment.transaction', 'acquirer_id', company_id=user.company_id.id) or \ env['payment.acquirer'].search([('website_published', '=', True), ('company_id', '=', user.company_id.id)])[0].id acquirer = env['payment.acquirer'].with_context(submit_class='btn btn-primary pull-right', diff --git a/addons/purchase_requisition/data/purchase_requisition_data.xml b/addons/purchase_requisition/data/purchase_requisition_data.xml index a6e3f90d239..8028122da41 100644 --- a/addons/purchase_requisition/data/purchase_requisition_data.xml +++ b/addons/purchase_requisition/data/purchase_requisition_data.xml @@ -3,7 +3,7 @@ diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 3cbcab51647..c7fe89edf21 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -38,7 +38,7 @@ class Website(models.Model): return self.env['res.lang'].search([]).ids def _default_language(self): - lang_code = self.env['ir.values'].get_default('res.partner', 'lang') + lang_code = self.env['ir.default'].get('res.partner', 'lang') def_lang = self.env['res.lang'].search([('code', '=', lang_code)], limit=1) return def_lang.id if def_lang else self._active_languages()[0] diff --git a/addons/website_sale_stock/models/website.py b/addons/website_sale_stock/models/website.py index e00d29cec89..bee131093b7 100644 --- a/addons/website_sale_stock/models/website.py +++ b/addons/website_sale_stock/models/website.py @@ -10,8 +10,8 @@ class Website(models.Model): values = super(Website, self)._prepare_sale_order_values(partner, pricelist) if values['company_id']: warehouse_id = ( - self.env['ir.values'].get_default('sale.order', 'warehouse_id', company_id=values.get('company_id')) or - self.env['ir.values'].get_default('sale.order', 'warehouse_id') or + self.env['ir.default'].get('sale.order', 'warehouse_id', company_id=values.get('company_id')) or + self.env['ir.default'].get('sale.order', 'warehouse_id') or self.env['stock.warehouse'].sudo().search([('company_id', '=', values['company_id'])], limit=1).id ) if warehouse_id: diff --git a/addons/website_sale_stock/models/website_config_settings.py b/addons/website_sale_stock/models/website_config_settings.py index b288aaee7c7..a731d057764 100644 --- a/addons/website_sale_stock/models/website_config_settings.py +++ b/addons/website_sale_stock/models/website_config_settings.py @@ -18,14 +18,14 @@ class WebsiteConfigSettings(models.TransientModel): @api.multi def set_values(self): super(WebsiteConfigSettings, self).set_values() - self.env['ir.values'].sudo().set_default('product.template', 'inventory_availability', self.inventory_availability) - self.env['ir.values'].sudo().set_default('product.template', 'available_threshold', self.available_threshold if self.inventory_availability == 'threshold' else None) + IrDefault = self.env['ir.default'].sudo() + IrDefault.set('product.template', 'inventory_availability', self.inventory_availability) + IrDefault.set('product.template', 'available_threshold', self.available_threshold if self.inventory_availability == 'threshold' else None) @api.model def get_values(self): res = super(WebsiteConfigSettings, self).get_values() - param = self.env['ir.values'].sudo() - res.update(inventory_availability=param.get_default('product.template', 'inventory_availability') or 'never', - available_threshold=param.get_default('product.template', 'available_threshold') or 5.0 - ) + IrDefault = self.env['ir.default'].sudo() + res.update(inventory_availability=IrDefault.get('product.template', 'inventory_availability') or 'never', + available_threshold=IrDefault.get('product.template', 'available_threshold') or 5.0) return res diff --git a/odoo/addons/base/__manifest__.py b/odoo/addons/base/__manifest__.py index be3a4b6af25..493f9f37c2a 100644 --- a/odoo/addons/base/__manifest__.py +++ b/odoo/addons/base/__manifest__.py @@ -33,6 +33,7 @@ The kernel of Odoo, needed for all installation. 'ir/ir_ui_menu_view.xml', 'ir/ir_ui_view_view.xml', 'ir/ir_values_view.xml', + 'ir/ir_default_view.xml', 'ir/ir_autovacuum.xml', 'ir/ir_model_report.xml', 'ir/report_ir_model.xml', diff --git a/odoo/addons/base/ir/__init__.py b/odoo/addons/base/ir/__init__.py index 747575c7bf4..194da2c4c2f 100644 --- a/odoo/addons/base/ir/__init__.py +++ b/odoo/addons/base/ir/__init__.py @@ -11,6 +11,7 @@ from . import ir_attachment from . import ir_cron from . import ir_filters from . import ir_values +from . import ir_default from . import ir_translation from . import ir_exports from . import ir_rule diff --git a/odoo/addons/base/ir/ir_default.py b/odoo/addons/base/ir/ir_default.py new file mode 100644 index 00000000000..e5f3f387c6b --- /dev/null +++ b/odoo/addons/base/ir/ir_default.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import json + +from odoo import api, fields, models, tools, _ +from odoo.exceptions import ValidationError + + +class IrDefault(models.Model): + """ User-defined default values for fields. """ + _name = 'ir.default' + _rec_name = 'field_id' + + field_id = fields.Many2one('ir.model.fields', string="Field", required=True, + ondelete='cascade', index=True) + user_id = fields.Many2one('res.users', string='User', ondelete='cascade', index=True, + help="If set, action binding only applies for this user.") + company_id = fields.Many2one('res.company', string='Company', ondelete='cascade', index=True, + help="If set, action binding only applies for this company") + condition = fields.Char('Condition', help="If set, applies the default upon condition.") + json_value = fields.Char('Default Value (JSON format)', required=True) + + @api.model + def create(self, vals): + self.clear_caches() + return super(IrDefault, self).create(vals) + + @api.multi + def write(self, vals): + if self: + self.clear_caches() + return super(IrDefault, self).write(vals) + + @api.multi + def unlink(self): + if self: + self.clear_caches() + return super(IrDefault, self).unlink() + + @api.model + def set(self, model_name, field_name, value, user_id=False, company_id=False, condition=False): + """ Defines a default value for the given field. Any entry for the same + scope (field, user, company) will be replaced. The value is encoded + in JSON to be stored to the database. + + :param user_id: may be ``False`` for all users, ``True`` for the + current user, or any user id + :param company_id: may be ``False`` for all companies, ``True`` for + the current user's company, or any company id + :param condition: optional condition that restricts the + applicability of the default value; this is an + opaque string, but the client typically uses + single-field conditions in the form ``'key=val'``. + """ + if user_id is True: + user_id = self.env.uid + if company_id is True: + company_id = self.env.user.company_id.id + + # check consistency of model_name, field_name, and value + try: + model = self.env[model_name] + field = model._fields[field_name] + field.convert_to_cache(value, model) + json_value = json.dumps(value, ensure_ascii=False) + except KeyError: + raise ValidationError(_("Invalid field %s.%s") % (model_name, field_name)) + except Exception: + raise ValidationError(_("Invalid value for %s.%s: %s") % (model_name, field_name, value)) + + # update existing default for the same scope, or create one + field = self.env['ir.model.fields']._get(model_name, field_name) + default = self.search([ + ('field_id', '=', field.id), + ('user_id', '=', user_id), + ('company_id', '=', company_id), + ('condition', '=', condition), + ]) + if default: + default.write({'json_value': json_value}) + else: + self.create({ + 'field_id': field.id, + 'user_id': user_id, + 'company_id': company_id, + 'condition': condition, + 'json_value': json_value, + }) + return True + + @api.model + def get(self, model_name, field_name, user_id=False, company_id=False, condition=False): + """ Return the default value for the given field, user and company, or + ``None`` if no default is available. + + :param user_id: may be ``False`` for all users, ``True`` for the + current user, or any user id + :param company_id: may be ``False`` for all companies, ``True`` for + the current user's company, or any company id + :param condition: optional condition that restricts the + applicability of the default value; this is an + opaque string, but the client typically uses + single-field conditions in the form ``'key=val'``. + """ + if user_id is True: + user_id = self.env.uid + if company_id is True: + company_id = self.env.user.company_id.id + + field = self.env['ir.model.fields']._get(model_name, field_name) + default = self.search([ + ('field_id', '=', field.id), + ('user_id', '=', user_id), + ('company_id', '=', company_id), + ('condition', '=', condition), + ], limit=1) + return json.loads(default.json_value) if default else None + + @api.model + @tools.ormcache('self.env.uid', 'model_name', 'condition') + # Note about ormcache invalidation: it is not needed when deleting a field, + # a user, or a company, as the corresponding defaults will no longer be + # requested. It must only be done when a user's company is modified. + def get_model_defaults(self, model_name, condition=False): + """ Return the available default values for the given model (for the + current user), as a dict mapping field names to values. + """ + cr = self.env.cr + query = """ SELECT f.name, d.json_value FROM ir_default d + JOIN ir_model_fields f ON d.field_id=f.id + JOIN res_users u ON u.id=%s + WHERE f.model=%s + AND (d.user_id IS NULL OR d.user_id=u.id) + AND (d.company_id IS NULL OR d.company_id=u.company_id) + AND {} + ORDER BY d.user_id, d.company_id, d.id + """ + params = [self.env.uid, model_name] + if condition: + query = query.format("d.condition=%s") + params.append(condition) + else: + query = query.format("d.condition IS NULL") + cr.execute(query, params) + result = {} + for row in cr.fetchall(): + # keep the highest priority default for each field + if row[0] not in result: + result[row[0]] = json.loads(row[1]) + return result + + @api.model + def discard_records(self, records): + """ Discard all the defaults of many2one fields using any of the given + records. + """ + json_vals = [json.dumps(id) for id in records.ids] + domain = [('field_id.ttype', '=', 'many2one'), + ('field_id.relation', '=', records._name), + ('json_value', 'in', json_vals)] + return self.search(domain).unlink() + + @api.model + def discard_values(self, model_name, field_name, values): + """ Discard all the defaults for any of the given values. """ + field = self.env['ir.model.fields']._get(model_name, field_name) + json_vals = [json.dumps(value, ensure_ascii=False) for value in values] + domain = [('field_id', '=', field.id), ('json_value', 'in', json_vals)] + return self.search(domain).unlink() diff --git a/odoo/addons/base/ir/ir_default_view.xml b/odoo/addons/base/ir/ir_default_view.xml new file mode 100644 index 00000000000..ad347d4a35a --- /dev/null +++ b/odoo/addons/base/ir/ir_default_view.xml @@ -0,0 +1,62 @@ + + + + + ir.default form view + ir.default + +
+ + + + + + + + + + +
+
+
+ + + ir.default tree view + ir.default + + + + + + + + + + + + ir.default search view + ir.default + + + + + + + + + + + + + + + User-defined Defaults + ir.actions.act_window + ir.default + form + tree,form + + + + +
diff --git a/odoo/addons/base/res/res_config.py b/odoo/addons/base/res/res_config.py index 7fd426ed2c5..d1efaa5c91a 100644 --- a/odoo/addons/base/res/res_config.py +++ b/odoo/addons/base/res/res_config.py @@ -469,14 +469,14 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) @api.model def default_get(self, fields): - IrValues = self.env['ir.values'] + IrDefault = self.env['ir.default'] classified = self._get_classified_fields() res = super(ResConfigSettings, self).default_get(fields) # defaults: take the corresponding default value they set for name, model, field in classified['default']: - value = IrValues.get_default(model, field) + value = IrDefault.get(model, field) if value is not None: res[name] = value @@ -517,7 +517,7 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) classified = self._get_classified_fields() # default values fields - IrValues = self.env['ir.values'].sudo() + IrDefault = self.env['ir.default'].sudo() for name, model, field in classified['default']: if isinstance(self[name], models.BaseModel): if self._fields[name].type == 'many2one': @@ -526,7 +526,7 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) value = self[name].ids else: value = self[name] - IrValues.set_default(model, field, value) + IrDefault.set(model, field, value) # group fields: modify group / implied groups for name, groups, implied_group in classified['group']: diff --git a/odoo/addons/base/res/res_lang.py b/odoo/addons/base/res/res_lang.py index 1cdc8a5db43..38737df2cb0 100644 --- a/odoo/addons/base/res/res_lang.py +++ b/odoo/addons/base/res/res_lang.py @@ -163,10 +163,10 @@ class Lang(models.Model): lang = self.search([('code', '=', lang_code)]) if not lang: self.load_lang(lang_code) - IrValues = self.env['ir.values'] - default_value = IrValues.get_defaults('res.partner', condition=False) - if not default_value: - IrValues.set_default('res.partner', 'lang', lang_code, condition=False) + IrDefault = self.env['ir.default'] + default_value = IrDefault.get('res.partner', 'lang') + if default_value is None: + IrDefault.set('res.partner', 'lang', lang_code) # set language of main company, created directly by db bootstrap SQL partner = self.env.user.company_id.partner_id if not partner.lang: @@ -219,13 +219,8 @@ class Lang(models.Model): if vals.get('active') == False: if self.env['res.users'].search([('lang', 'in', lang_codes)]): raise UserError(_("Cannot unactivate a language that is currently used by users.")) - # delete linked ir.value specifying default partner's language - default_lang = self.env['ir.values'].search([ - ('key', '=', 'default'), - ('name', '=', 'lang'), - ('model', '=', 'res.partner')]) - if default_lang and default_lang.value_unpickle in lang_codes: - default_lang.unlink() + # delete linked ir.default specifying default partner's language + self.env['ir.default'].discard_values('res.partner', 'lang', lang_codes) res = super(Lang, self).write(vals) self.clear_caches() diff --git a/odoo/addons/base/res/res_users.py b/odoo/addons/base/res/res_users.py index f0a98285a85..ff200716a59 100644 --- a/odoo/addons/base/res/res_users.py +++ b/odoo/addons/base/res/res_users.py @@ -355,7 +355,7 @@ class Users(models.Model): if user.partner_id.company_id and user.partner_id.company_id.id != values['company_id']: user.partner_id.write({'company_id': user.company_id.id}) # clear default ir values when company changes - self.env['ir.values'].get_defaults_dict.clear_cache(self.env['ir.values']) + self.env['ir.default'].clear_caches() # clear caches linked to the users if 'groups_id' in values: diff --git a/odoo/addons/base/security/base_security.xml b/odoo/addons/base/security/base_security.xml index f36c0b061e8..cbfcff5e0a4 100644 --- a/odoo/addons/base/security/base_security.xml +++ b/odoo/addons/base/security/base_security.xml @@ -111,6 +111,22 @@
+ + Defaults: alter personal defaults + + [('user_id','=',user.id)] + + + + + + Defaults: alter all defaults + + [(1,'=',1)] + + + + ir.ui.view_custom rule diff --git a/odoo/addons/base/security/ir.model.access.csv b/odoo/addons/base/security/ir.model.access.csv index e68f18d34b5..656ba20cbc4 100644 --- a/odoo/addons/base/security/ir.model.access.csv +++ b/odoo/addons/base/security/ir.model.access.csv @@ -33,6 +33,9 @@ "access_ir_ui_view_group_system","ir_ui_view group_system","model_ir_ui_view","group_system",1,1,1,1 "access_ir_ui_view_custom_group_user","ir_ui_view_custom_group_user","model_ir_ui_view_custom",,1,1,1,1 "access_ir_values_group_all","ir_values group_all","model_ir_values",,1,1,1,1 +"access_ir_default_all","ir_default all","model_ir_default",,1,0,0,0 +"access_ir_default_group_user","ir_default group_user","model_ir_default","group_user",1,1,1,1 +"access_ir_default_group_system","ir_default group_system","model_ir_default","group_system",1,1,1,1 "access_res_company_group_erp_manager","res_company group_erp_manager","model_res_company","group_erp_manager",1,1,1,1 "access_res_company_group_user","res_company group_user","model_res_company",,1,0,0,0 "access_res_country_group_all","res_country group_user_all","model_res_country",,1,0,0,0 diff --git a/odoo/addons/base/tests/__init__.py b/odoo/addons/base/tests/__init__.py index 1b6fe723b2b..daba366b3f8 100644 --- a/odoo/addons/base/tests/__init__.py +++ b/odoo/addons/base/tests/__init__.py @@ -13,6 +13,7 @@ from . import test_ir_filters from . import test_ir_sequence from . import test_ir_sequence_date_range from . import test_ir_values +from . import test_ir_default from . import test_mail from . import test_menu from . import test_mimetypes diff --git a/odoo/addons/base/tests/test_ir_default.py b/odoo/addons/base/tests/test_ir_default.py new file mode 100644 index 00000000000..be5ce7d7236 --- /dev/null +++ b/odoo/addons/base/tests/test_ir_default.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.exceptions import ValidationError +from odoo.tests.common import TransactionCase + + +class TestIrDefault(TransactionCase): + + def test_defaults(self): + """ check the mechanism of user-defined defaults """ + companyA = self.env.user.company_id + companyB = companyA.create({'name': 'CompanyB'}) + user1 = self.env.user + user2 = user1.create({'name': 'u2', 'login': 'u2'}) + user3 = user1.create({'name': 'u3', 'login': 'u3', + 'company_id': companyB.id, + 'company_ids': companyB.ids}) + + # create some default value for some model + IrDefault1 = self.env['ir.default'] + IrDefault2 = IrDefault1.sudo(user2) + IrDefault3 = IrDefault1.sudo(user3) + + # set a default value for all users + IrDefault1.search([('field_id.model', '=', 'res.partner')]).unlink() + IrDefault1.set('res.partner', 'ref', 'GLOBAL', user_id=False, company_id=False) + self.assertEqual(IrDefault1.get_model_defaults('res.partner'), {'ref': 'GLOBAL'}, + "Can't retrieve the created default value for all users.") + self.assertEqual(IrDefault2.get_model_defaults('res.partner'), {'ref': 'GLOBAL'}, + "Can't retrieve the created default value for all users.") + self.assertEqual(IrDefault3.get_model_defaults('res.partner'), {'ref': 'GLOBAL'}, + "Can't retrieve the created default value for all users.") + + # set a default value for current company (behavior of 'set default' from debug mode) + IrDefault1.set('res.partner', 'ref', 'COMPANY', user_id=False, company_id=True) + self.assertEqual(IrDefault1.get_model_defaults('res.partner'), {'ref': 'COMPANY'}, + "Can't retrieve the created default value for company.") + self.assertEqual(IrDefault2.get_model_defaults('res.partner'), {'ref': 'COMPANY'}, + "Can't retrieve the created default value for company.") + self.assertEqual(IrDefault3.get_model_defaults('res.partner'), {'ref': 'GLOBAL'}, + "Unexpected default value for company.") + + # set a default value for current user (behavior of 'set default' from debug mode) + IrDefault2.set('res.partner', 'ref', 'USER', user_id=True, company_id=True) + self.assertEqual(IrDefault1.get_model_defaults('res.partner'), {'ref': 'COMPANY'}, + "Can't retrieve the created default value for user.") + self.assertEqual(IrDefault2.get_model_defaults('res.partner'), {'ref': 'USER'}, + "Unexpected default value for user.") + self.assertEqual(IrDefault3.get_model_defaults('res.partner'), {'ref': 'GLOBAL'}, + "Unexpected default value for company.") + + # check default values on partners + default1 = IrDefault1.env['res.partner'].default_get(['ref']).get('ref') + self.assertEqual(default1, 'COMPANY', "Wrong default value.") + default2 = IrDefault2.env['res.partner'].default_get(['ref']).get('ref') + self.assertEqual(default2, 'USER', "Wrong default value.") + default3 = IrDefault3.env['res.partner'].default_get(['ref']).get('ref') + self.assertEqual(default3, 'GLOBAL', "Wrong default value.") + + def test_conditions(self): + """ check user-defined defaults with condition """ + IrDefault = self.env['ir.default'] + + # default without condition + IrDefault.search([('field_id.model', '=', 'res.partner')]).unlink() + IrDefault.set('res.partner', 'ref', 'X') + self.assertEqual(IrDefault.get_model_defaults('res.partner'), + {'ref': 'X'}) + self.assertEqual(IrDefault.get_model_defaults('res.partner', condition='name=Agrolait'), + {}) + + # default with a condition + IrDefault.search([('field_id.model', '=', 'res.partner.title')]).unlink() + IrDefault.set('res.partner.title', 'shortcut', 'X') + IrDefault.set('res.partner.title', 'shortcut', 'Mr', condition='name=Mister') + self.assertEqual(IrDefault.get_model_defaults('res.partner.title'), + {'shortcut': 'X'}) + self.assertEqual(IrDefault.get_model_defaults('res.partner.title', condition='name=Miss'), + {}) + self.assertEqual(IrDefault.get_model_defaults('res.partner.title', condition='name=Mister'), + {'shortcut': 'Mr'}) + + def test_invalid(self): + """ check error cases with 'ir.default' """ + IrDefault = self.env['ir.default'] + with self.assertRaises(ValidationError): + IrDefault.set('unknown_model', 'unknown_field', 42) + with self.assertRaises(ValidationError): + IrDefault.set('res.partner', 'unknown_field', 42) + with self.assertRaises(ValidationError): + IrDefault.set('res.partner', 'lang', 'some_LANG') + with self.assertRaises(ValidationError): + IrDefault.set('res.partner', 'credit_limit', 'foo') + + def test_removal(self): + """ check defaults for many2one with their value being removed """ + IrDefault = self.env['ir.default'] + IrDefault.search([('field_id.model', '=', 'res.partner')]).unlink() + + # set a record as a default value + title = self.env['res.partner.title'].create({'name': 'President'}) + IrDefault.set('res.partner', 'title', title.id) + self.assertEqual(IrDefault.get_model_defaults('res.partner'), {'title': title.id}) + + # delete the record, and check the presence of the default value + title.unlink() + self.assertEqual(IrDefault.get_model_defaults('res.partner'), {}) diff --git a/odoo/models.py b/odoo/models.py index d4e184ccdf2..ee5329cf9d7 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -975,6 +975,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): defaults = {} parent_fields = defaultdict(list) + ir_defaults = self.env['ir.default'].get_model_defaults(self._name) for name in fields_list: # 1. look up context @@ -983,11 +984,9 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): defaults[name] = self._context[key] continue - # 2. look up ir_values - # Note: performance is good, because get_defaults_dict is cached! - ir_values_dict = self.env['ir.values'].get_defaults_dict(self._name) - if name in ir_values_dict: - defaults[name] = ir_values_dict[name] + # 2. look up ir.default + if name in ir_defaults: + defaults[name] = ir_defaults[name] continue field = self._fields.get(name) @@ -2045,7 +2044,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): def _init_column(self, column_name): """ Initialize the value of the given column for existing rows. """ # get the default value; ideally, we should use default_get(), but it - # fails due to ir.values not being ready + # fails due to ir.default not being ready field = self._fields[column_name] if field.default: value = field.default(self) @@ -2842,6 +2841,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): cr = self._cr Data = self.env['ir.model.data'].sudo().with_context({}) Values = self.env['ir.values'] + Defaults = self.env['ir.default'].sudo() Attachment = self.env['ir.attachment'] for sub_ids in cr.split_for_in_conditions(self.ids): @@ -2867,6 +2867,10 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): if values: values.unlink() + # For the same reason, remove the defaults having some of the + # records as value + Defaults.discard_records(self.browse(sub_ids)) + # For the same reason, remove the relevant records in ir_attachment # (the search is performed with sql as the search method of # ir_attachment is overridden to hide attachments of deleted