diff --git a/addons/mail/models/ir_actions.py b/addons/mail/models/ir_actions.py index 9156baf136c..9ed73296701 100644 --- a/addons/mail/models/ir_actions.py +++ b/addons/mail/models/ir_actions.py @@ -63,8 +63,7 @@ class ServerActions(models.Model): if action.state == 'next_activity' and not action.model_id.is_mail_thread: raise ValidationError(_("A next activity can only be planned on models that use the chatter")) - @api.model - def run_action_followers_multi(self, action, eval_context=None): + def _run_action_followers_multi(self, action, eval_context=None): Model = self.env[action.model_name] if self.partner_ids or self.channel_ids and hasattr(Model, 'message_subscribe'): records = Model.browse(self._context.get('active_ids', self._context.get('active_id'))) @@ -98,8 +97,7 @@ class ServerActions(models.Model): return True return False - @api.model - def run_action_email(self, action, eval_context=None): + def _run_action_email(self, action, eval_context=None): # TDE CLEANME: when going to new api with server action, remove action if not action.template_id or not self._context.get('active_id') or self._is_recompute(action): return False @@ -111,8 +109,7 @@ class ServerActions(models.Model): action.template_id.with_context(cleaned_ctx).send_mail(self._context.get('active_id'), force_send=False, raise_exception=False) return False - @api.model - def run_action_next_activity(self, action, eval_context=None): + def _run_action_next_activity(self, action, eval_context=None): if not action.activity_type_id or not self._context.get('active_id') or self._is_recompute(action): return False diff --git a/addons/sms/models/ir_actions.py b/addons/sms/models/ir_actions.py index e5421d4546c..dddaa885d74 100644 --- a/addons/sms/models/ir_actions.py +++ b/addons/sms/models/ir_actions.py @@ -26,8 +26,7 @@ class ServerActions(models.Model): if action.state == 'sms' and not action.model_id.is_mail_thread: raise ValidationError(_("Sending SMS can only be done on a mail.thread model")) - @api.model - def run_action_sms_multi(self, action, eval_context=None): + def _run_action_sms_multi(self, action, eval_context=None): # TDE CLEANME: when going to new api with server action, remove action if not action.sms_template_id or self._is_recompute(action): return False diff --git a/addons/website/models/ir_actions.py b/addons/website/models/ir_actions.py index e83426b2fef..7e795efe5a8 100644 --- a/addons/website/models/ir_actions.py +++ b/addons/website/models/ir_actions.py @@ -51,10 +51,10 @@ class ServerAction(models.Model): return eval_context @api.model - def run_action_code_multi(self, action, eval_context=None): + def _run_action_code_multi(self, action, eval_context=None): """ Override to allow returning response the same way action is already returned by the basic server action behavior. Note that response has priority over action, avoid using both. """ - res = super(ServerAction, self).run_action_code_multi(action, eval_context) + res = super(ServerAction, self)._run_action_code_multi(action, eval_context) return eval_context.get('response', res) diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py index 4ca22c47b5b..5ebe4e10203 100644 --- a/odoo/addons/base/models/ir_actions.py +++ b/odoo/addons/base/models/ir_actions.py @@ -420,6 +420,28 @@ class IrActionsServer(models.Model): if not self._check_m2m_recursion('child_ids'): raise ValidationError(_('Recursion found in child server actions')) + def _get_runner(self): + t = type(self) + fn = getattr(t, f'_run_action_{self.state}_multi', None)\ + or getattr(t, f'run_action_{self.state}_multi', None) + if fn: + return fn, True + + fn = getattr(t, f'_run_action_{self.state}', None)\ + or getattr(t, f'run_action_{self.state}', None) + return fn, False + + def _register_hook(self): + super()._register_hook() + + for cls in type(self).mro(): + for symbol in vars(cls).keys(): + if symbol.startswith('run_action_'): + _logger.warning( + "RPC-public action methods are deprecated, found %r (in class %s.%s)", + symbol, cls.__module__, cls.__name__ + ) + @api.onchange('crud_model_id') def _onchange_crud_model_id(self): self.link_field_id = False @@ -437,14 +459,12 @@ class IrActionsServer(models.Model): self.filtered('binding_model_id').write({'binding_model_id': False}) return True - @api.model - def run_action_code_multi(self, action, eval_context=None): + def _run_action_code_multi(self, action, eval_context=None): safe_eval(action.sudo().code.strip(), eval_context, mode="exec", nocopy=True) # nocopy allows to return 'action' if 'action' in eval_context: return eval_context['action'] - @api.model - def run_action_multi(self, action, eval_context=None): + def _run_action_multi(self, action, eval_context=None): res = False for act in action.child_ids.sorted(): result = act.run() @@ -452,8 +472,7 @@ class IrActionsServer(models.Model): res = result return res - @api.model - def run_action_object_write(self, action, eval_context=None): + def _run_action_object_write(self, action, eval_context=None): """Apply specified write changes to active_id.""" res = {} for exp in action.fields_lines: @@ -466,8 +485,7 @@ class IrActionsServer(models.Model): else: self.env[action.model_id.model].browse(self._context.get('active_id')).write(res) - @api.model - def run_action_object_create(self, action, eval_context=None): + def _run_action_object_create(self, action, eval_context=None): """Create specified model object with specified values. If applicable, link active_id. to the new record. @@ -485,7 +503,6 @@ class IrActionsServer(models.Model): else: record.write({action.link_field_id.name: res.id}) - @api.model def _get_eval_context(self, action=None): """ Prepare the context used when evaluating python code, like the python formulas or code server actions. @@ -528,8 +545,12 @@ class IrActionsServer(models.Model): def run(self): """ Runs the server action. For each server action, the - run_action_ method is called. This allows easy overriding - of the server actions. + :samp:`_run_action_{TYPE}[_multi]` method is called. This allows easy + overriding of the server actions. + + The `_multi` suffix means the runner can operate on multiple records, + otherwise if there are multiple records the runner will be called once + for each :param dict context: context should contain following keys @@ -551,30 +572,33 @@ class IrActionsServer(models.Model): raise AccessError(_("You don't have enough access rights to run this action.")) eval_context = self._get_eval_context(action) - if hasattr(self, 'run_action_%s_multi' % action.state): + + runner, multi = action._get_runner() + if runner and multi: # call the multi method run_self = self.with_context(eval_context['env'].context) - func = getattr(run_self, 'run_action_%s_multi' % action.state) - res = func(action, eval_context=eval_context) - - elif hasattr(self, 'run_action_%s' % action.state): + res = runner(run_self, action, eval_context=eval_context) + elif runner: active_id = self._context.get('active_id') if not active_id and self._context.get('onchange_self'): active_id = self._context['onchange_self']._origin.id if not active_id: # onchange on new record - func = getattr(self, 'run_action_%s' % action.state) - res = func(action, eval_context=eval_context) + res = runner(self, action, eval_context=eval_context) active_ids = self._context.get('active_ids', [active_id] if active_id else []) for active_id in active_ids: # run context dedicated to a particular active_id run_self = self.with_context(active_ids=[active_id], active_id=active_id) eval_context["env"].context = run_self._context - # call the single method related to the action: run_action_ - func = getattr(run_self, 'run_action_%s' % action.state) - res = func(action, eval_context=eval_context) + res = runner(run_self, action, eval_context=eval_context) + else: + _logger.warning( + "Found no way to execute server action %r of type %r, ignoring it. " + "Verify that the type is correct or add a method called " + "`_run_action_` or `_run_action__multi`.", + action.name, action.state + ) return res or False - @api.model def _run_actions(self, ids): """ Run server actions with given ids.