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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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