1# -*- coding: utf-8 -*- 2# Part of Odoo. See LICENSE file for full copyright and licensing details. 3 4import json 5 6from odoo import api, fields, models, tools, _ 7from odoo.exceptions import ValidationError 8 9 10class IrDefault(models.Model): 11 """ User-defined default values for fields. """ 12 _name = 'ir.default' 13 _description = 'Default Values' 14 _rec_name = 'field_id' 15 16 field_id = fields.Many2one('ir.model.fields', string="Field", required=True, 17 ondelete='cascade', index=True) 18 user_id = fields.Many2one('res.users', string='User', ondelete='cascade', index=True, 19 help="If set, action binding only applies for this user.") 20 company_id = fields.Many2one('res.company', string='Company', ondelete='cascade', index=True, 21 help="If set, action binding only applies for this company") 22 condition = fields.Char('Condition', help="If set, applies the default upon condition.") 23 json_value = fields.Char('Default Value (JSON format)', required=True) 24 25 @api.model_create_multi 26 def create(self, vals_list): 27 self.clear_caches() 28 return super(IrDefault, self).create(vals_list) 29 30 def write(self, vals): 31 if self: 32 self.clear_caches() 33 return super(IrDefault, self).write(vals) 34 35 def unlink(self): 36 if self: 37 self.clear_caches() 38 return super(IrDefault, self).unlink() 39 40 @api.model 41 def set(self, model_name, field_name, value, user_id=False, company_id=False, condition=False): 42 """ Defines a default value for the given field. Any entry for the same 43 scope (field, user, company) will be replaced. The value is encoded 44 in JSON to be stored to the database. 45 46 :param user_id: may be ``False`` for all users, ``True`` for the 47 current user, or any user id 48 :param company_id: may be ``False`` for all companies, ``True`` for 49 the current user's company, or any company id 50 :param condition: optional condition that restricts the 51 applicability of the default value; this is an 52 opaque string, but the client typically uses 53 single-field conditions in the form ``'key=val'``. 54 """ 55 if user_id is True: 56 user_id = self.env.uid 57 if company_id is True: 58 company_id = self.env.company.id 59 60 # check consistency of model_name, field_name, and value 61 try: 62 model = self.env[model_name] 63 field = model._fields[field_name] 64 field.convert_to_cache(value, model) 65 json_value = json.dumps(value, ensure_ascii=False) 66 except KeyError: 67 raise ValidationError(_("Invalid field %s.%s") % (model_name, field_name)) 68 except Exception: 69 raise ValidationError(_("Invalid value for %s.%s: %s") % (model_name, field_name, value)) 70 71 # update existing default for the same scope, or create one 72 field = self.env['ir.model.fields']._get(model_name, field_name) 73 default = self.search([ 74 ('field_id', '=', field.id), 75 ('user_id', '=', user_id), 76 ('company_id', '=', company_id), 77 ('condition', '=', condition), 78 ]) 79 if default: 80 default.write({'json_value': json_value}) 81 else: 82 self.create({ 83 'field_id': field.id, 84 'user_id': user_id, 85 'company_id': company_id, 86 'condition': condition, 87 'json_value': json_value, 88 }) 89 return True 90 91 @api.model 92 def get(self, model_name, field_name, user_id=False, company_id=False, condition=False): 93 """ Return the default value for the given field, user and company, or 94 ``None`` if no default is available. 95 96 :param user_id: may be ``False`` for all users, ``True`` for the 97 current user, or any user id 98 :param company_id: may be ``False`` for all companies, ``True`` for 99 the current user's company, or any company id 100 :param condition: optional condition that restricts the 101 applicability of the default value; this is an 102 opaque string, but the client typically uses 103 single-field conditions in the form ``'key=val'``. 104 """ 105 if user_id is True: 106 user_id = self.env.uid 107 if company_id is True: 108 company_id = self.env.company.id 109 110 field = self.env['ir.model.fields']._get(model_name, field_name) 111 default = self.search([ 112 ('field_id', '=', field.id), 113 ('user_id', '=', user_id), 114 ('company_id', '=', company_id), 115 ('condition', '=', condition), 116 ], limit=1) 117 return json.loads(default.json_value) if default else None 118 119 @api.model 120 @tools.ormcache('self.env.uid', 'self.env.company.id', 'model_name', 'condition') 121 # Note about ormcache invalidation: it is not needed when deleting a field, 122 # a user, or a company, as the corresponding defaults will no longer be 123 # requested. It must only be done when a user's company is modified. 124 def get_model_defaults(self, model_name, condition=False): 125 """ Return the available default values for the given model (for the 126 current user), as a dict mapping field names to values. 127 """ 128 cr = self.env.cr 129 query = """ SELECT f.name, d.json_value 130 FROM ir_default d 131 JOIN ir_model_fields f ON d.field_id=f.id 132 WHERE f.model=%s 133 AND (d.user_id IS NULL OR d.user_id=%s) 134 AND (d.company_id IS NULL OR d.company_id=%s) 135 AND {} 136 ORDER BY d.user_id, d.company_id, d.id 137 """ 138 # self.env.company is empty when there is no user (controllers with auth=None) 139 params = [model_name, self.env.uid, self.env.company.id or None] 140 if condition: 141 query = query.format("d.condition=%s") 142 params.append(condition) 143 else: 144 query = query.format("d.condition IS NULL") 145 cr.execute(query, params) 146 result = {} 147 for row in cr.fetchall(): 148 # keep the highest priority default for each field 149 if row[0] not in result: 150 result[row[0]] = json.loads(row[1]) 151 return result 152 153 @api.model 154 def discard_records(self, records): 155 """ Discard all the defaults of many2one fields using any of the given 156 records. 157 """ 158 json_vals = [json.dumps(id) for id in records.ids] 159 domain = [('field_id.ttype', '=', 'many2one'), 160 ('field_id.relation', '=', records._name), 161 ('json_value', 'in', json_vals)] 162 return self.search(domain).unlink() 163 164 @api.model 165 def discard_values(self, model_name, field_name, values): 166 """ Discard all the defaults for any of the given values. """ 167 field = self.env['ir.model.fields']._get(model_name, field_name) 168 json_vals = [json.dumps(value, ensure_ascii=False) for value in values] 169 domain = [('field_id', '=', field.id), ('json_value', 'in', json_vals)] 170 return self.search(domain).unlink() 171