# Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models, _ from odoo.exceptions import UserError from ast import literal_eval class IrEmbeddedActions(models.Model): _name = 'ir.embedded.actions' _description = 'Embedded Actions' _order = 'sequence, id' name = fields.Char(translate=True) sequence = fields.Integer() parent_action_id = fields.Many2one('ir.actions.act_window', required=True, string='Parent Action', ondelete="cascade") parent_res_id = fields.Integer(string="Active Parent Id") parent_res_model = fields.Char(string='Active Parent Model', required=True) # It is required to have either action_id or python_method action_id = fields.Many2one('ir.actions.actions', string="Action", ondelete="cascade") python_method = fields.Char(help="Python method returning an action") user_id = fields.Many2one('res.users', string="User", help="User specific embedded action. If empty, shared embedded action", ondelete="cascade") is_deletable = fields.Boolean(compute="_compute_is_deletable") default_view_mode = fields.Char(string="Default View", help="Default view (if none, default view of the action is taken)") filter_ids = fields.One2many("ir.filters", "embedded_action_id", help="Default filter of the embedded action (if none, no filters)") is_visible = fields.Boolean(string="Embedded visibility", help="Computed field to check if the record should be visible according to the domain", compute="_compute_is_visible") domain = fields.Char(default="[]", help="Domain applied to the active id of the parent model") context = fields.Char(default="{}", help="Context dictionary as Python expression, empty by default (Default: {})") groups_ids = fields.Many2many('res.groups', help='Groups that can execute the embedded action. Leave empty to allow everybody.') _sql_constraints = [ ( 'check_only_one_action_defined', """CHECK( (action_id IS NOT NULL AND python_method IS NULL) OR (action_id IS NULL AND python_method IS NOT NULL) )""", 'Constraint to ensure that either an XML action or a python_method is defined, but not both.' ), ( 'check_python_method_requires_name', """CHECK( NOT (python_method IS NOT NULL AND name IS NULL) )""", 'Constraint to ensure that if a python_method is defined, then the name must also be defined.' ) ] @api.model_create_multi def create(self, vals_list): # The name by default is computed based on the triggered action if a action_id is defined. for vals in vals_list: if "name" not in vals: vals["name"] = self.env["ir.actions.actions"].browse(vals["action_id"]).name if "python_method" in vals and "action_id" in vals: if vals.get("python_method"): # then remove the action_id since the action surely given by the python method. del vals["action_id"] else: # remove python_method in the vals since the vals is falsy. del vals["python_method"] return super().create(vals_list) # The record is deletable if it hasn't been created from a xml record (i.e. is not a default embedded action) def _compute_is_deletable(self): external_ids = self._get_external_ids() for record in self: record_external_ids = external_ids[record.id] record.is_deletable = all( ex_id.startswith(("__export__", "__custom__")) for ex_id in record_external_ids ) # Compute if the record should be visible to the user based on the domain applied to the active id of the parent # model and based on the groups allowed to access the record. def _compute_is_visible(self): active_id = self.env.context.get("active_id", False) if not active_id: self.is_visible = False return domain_id = [("id", "=", active_id)] for parent_res_model, records in self.grouped('parent_res_model').items(): active_model_record = self.env[parent_res_model].search(domain_id, order='id') for record in records: action_groups = record.groups_ids if not action_groups or (action_groups & self.env.user.groups_id): domain_model = literal_eval(record.domain) record.is_visible = ( record.parent_res_id in (False, self.env.context.get('active_id', False)) and record.user_id.id in (False, self.env.uid) and active_model_record.filtered_domain(domain_model) ) else: record.is_visible = False # Delete the filters linked to a embedded action. @api.ondelete(at_uninstall=False) def _unlink_if_action_deletable(self): for record in self: if not record.is_deletable: raise UserError(_('You cannot delete a default embedded action')) def _get_readable_fields(self): """ return the list of fields that are safe to read """ return { "name", "parent_action_id", "parent_res_id", "parent_res_model", "action_id", "python_method", "user_id", "is_deletable", "default_view_mode", "filter_ids", "domain", "context", "groups_ids" }