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