From bcc0212bfb221976864bbf40038a9093232ce029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 14 Dec 2016 10:28:15 +0100 Subject: [PATCH 01/11] [IMP] ir_actions: perform small code cleaning * use selection and selection_add to replace old indirection for state field and ease code understanding * update various strings and helps * update Python code helper to match new API changes, notably model, record and records being recordsets --- addons/mail/models/ir_actions.py | 8 +----- odoo/addons/base/ir/ir_actions.py | 47 ++++++++++++++----------------- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/addons/mail/models/ir_actions.py b/addons/mail/models/ir_actions.py index 015e3f02b92..33056302562 100644 --- a/addons/mail/models/ir_actions.py +++ b/addons/mail/models/ir_actions.py @@ -10,12 +10,7 @@ class ServerActions(models.Model): _name = 'ir.actions.server' _inherit = ['ir.actions.server'] - @api.model - def _get_states(self): - res = super(ServerActions, self)._get_states() - res.insert(0, ('email', 'Send Email')) - return res - + state = fields.Selection(selection_add=[('email', 'Send Email')]) email_from = fields.Char('From', related='template_id.email_from', readonly=True) email_to = fields.Char('To (Emails)', related='template_id.email_to', readonly=True) partner_to = fields.Char('To (Partners)', related='template_id.partner_to', readonly=True) @@ -52,4 +47,3 @@ class ServerActions(models.Model): ctx['mail_notify_force_send'] = False eval_context['env'].context = ctx return eval_context - diff --git a/odoo/addons/base/ir/ir_actions.py b/odoo/addons/base/ir/ir_actions.py index b0ee6d981d2..aff2fe546ea 100644 --- a/odoo/addons/base/ir/ir_actions.py +++ b/odoo/addons/base/ir/ir_actions.py @@ -444,9 +444,9 @@ class IrActionsServer(models.Model): DEFAULT_PYTHON_CODE = """# Available variables: # - time, datetime, dateutil, timezone: Python libraries # - env: Odoo Environement -# - model: Model of the record on which the action is triggered -# - record: Record on which the action is triggered if there is one, otherwise None -# - records: Records on which the action is triggered if there is one, otherwise None +# - model: Odoo Model of the record on which the action is triggered; is a void recordset +# - record: record set of the current record on which the action is triggered; may be be void +# - records: record set of all records on which the action is triggered; may be void # - log : log(message), function to log debug information in logging table # - Warning: Warning Exception to use with raise # To return an action, assign: action = {...}\n\n\n\n""" @@ -456,17 +456,6 @@ class IrActionsServer(models.Model): records = self.env['ir.model'].search([]) return [(record.model, record.name) for record in records] + [('', '')] - @api.model - def _get_states(self): - """ Override me in order to add new states in the server action. Please - note that the added key length should not be higher than already-existing - ones. """ - return [('code', 'Execute Python Code'), - ('client_action', 'Run a Client Action'), - ('object_create', 'Create or Copy a new Record'), - ('object_write', 'Write on a Record'), - ('multi', 'Execute several actions')] - name = fields.Char(string='Action Name', translate=True) type = fields.Char(default='ir.actions.server') @@ -476,24 +465,30 @@ class IrActionsServer(models.Model): "a Python expression, like 'record.list_price > 5000'. A void " "condition is considered as always True. Help about python expression " "is given in the help tab.") - state = fields.Selection(selection=lambda self: self._get_states(), - string='Action To Do', default='code', required=True, - help="Type of server action. The following values are available:\n" - "- 'Execute Python Code': a block of python code that will be executed\n" - "- 'Run a Client Action': choose a client action to launch\n" - "- 'Create or Copy a new Record': create a new record with new values, or copy an existing record in your database\n" - "- 'Write on a Record': update the values of a record\n" - "- 'Execute several actions': define an action that triggers several other server actions\n" - "- 'Send Email': automatically send an email (available in email_template)") + state = fields.Selection([ + ('code', 'Execute Python Code'), + ('client_action', 'Run a Client Action'), + ('object_create', 'Create or Copy a new Record'), + ('object_write', 'Write on a Record'), + ('multi', 'Execute several actions')], string='Action To Do', + default='code', required=True, # set default to write ? + help="Type of server action. The following values are available:\n" + "- 'Execute Python Code': a block of python code that will be executed\n" + "- 'Run a Client Action': choose a client action to launch\n" + "- 'Create or Copy a new Record': create a new record with new values, or copy an existing record in your database\n" + "- 'Write on a Record': update the values of a record\n" + "- 'Execute several actions': define an action that triggers several other server actions\n" + "- 'Send Email': automatically send an email (available in email_template)") # Generic sequence = fields.Integer(default=5, help="When dealing with multiple actions, the execution order is " "based on the sequence. Low number means high priority.") model_id = fields.Many2one('ir.model', string='Base Model', required=True, ondelete='cascade', help="Base model on which the server action runs.") - model_name = fields.Char(related='model_id.model', readonly=True) - menu_ir_values_id = fields.Many2one('ir.values', string='More Menu entry', readonly=True, - help='More menu entry.', copy=False) + model_name = fields.Char(related='model_id.model', readonly=True, store=True) + menu_ir_values_id = fields.Many2one('ir.values', string='Action on Object', + copy=False, readonly=True, + help='IrValues entry of the related more menu entry action') # Client Action action_id = fields.Many2one('ir.actions.actions', string='Client Action', help="Select the client action that has to be executed.") From 452a97d9d44771d868e5475e3323e1c8964b4740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 3 Jan 2017 17:09:57 +0100 Subject: [PATCH 02/11] [REF] ir_actions: remove usage field This field is now informative only and not used through the codebase. As an usage field could be defined on various inheriting ir.actions.* models, let us remove it from the base model and give some freedom to sub-models. --- odoo/addons/base/ir/ir_actions.py | 1 - odoo/addons/base/ir/ir_actions.xml | 2 -- 2 files changed, 3 deletions(-) diff --git a/odoo/addons/base/ir/ir_actions.py b/odoo/addons/base/ir/ir_actions.py index aff2fe546ea..3c458a7c723 100644 --- a/odoo/addons/base/ir/ir_actions.py +++ b/odoo/addons/base/ir/ir_actions.py @@ -24,7 +24,6 @@ class IrActions(models.Model): name = fields.Char(required=True) type = fields.Char(string='Action Type', required=True) - usage = fields.Char(string='Action Usage') xml_id = fields.Char(compute='_compute_xml_id', string="External ID") help = fields.Html(string='Action Description', help='Optional help text for the users with a description of the target view, such as its usage and purpose.', diff --git a/odoo/addons/base/ir/ir_actions.xml b/odoo/addons/base/ir/ir_actions.xml index 0560e95dd59..488434c5333 100644 --- a/odoo/addons/base/ir/ir_actions.xml +++ b/odoo/addons/base/ir/ir_actions.xml @@ -11,7 +11,6 @@ - @@ -85,7 +84,6 @@ - From 8fa9f7d6d4bc75bb78547358c3cb1c398b1a52ca Mon Sep 17 00:00:00 2001 From: Yannick Tivisse Date: Wed, 14 Dec 2016 10:38:16 +0100 Subject: [PATCH 03/11] [REF] ir_actions: refactor and simplify ir.actions.server Main idea of this commit is to simplify the various options of ir.actions.server model. Purpose is to make it a simple tool to execute some actions or python code. This has two main consequences * create and update server actions are simplified to update only the current record. Complex options can always be achieved by python code instead of error-prone option building mechanism; * condition of server actions is removed. If a condition is necessary it can either be included in a code server action, or integrated in an automated action; This commit features : * remove 'client_action' server action type. Indeed this can be achieved with a code server action returning an action; * remove 'condition' on ir_actions_server model. From now on conditions and triggers are managed in server action code or using automated or scheduled action; * clean 'create and copy a record'. Create and link are the proposed options. Copy of the record or of a selected record is removed. Link is simplified by removing the parsed expression that allowed complex path update. It is simpler to write code than write python-like expression in char fields; * remove 'use_write' options. It now simply updates a record; * remove 'write_expression' as it is replaced by code actions; * remove 'ref_object' not used anymore with the removal of "choose a record"; * remove helper to build an expression not used anymore with the removal of "write expression"; * remove object ID finder; * removed template display when choosing an email template; just go on the template if you want more details; * refactor server action form view to reflect changes. It will be used in future commits as base view for automated and scheduled actions; * add 'sequence' in list view; * add new 'usage' selection field allowing to distinguish which model uses the server action. Indeed as server action will soon be used in scheduled or automated actions having a clue about the usage is always helpful; Thanks to @fpodoo for the original idea and preliminary work. Thanks to @jpr-odoo for first developments. Thanks to @jem-odoo and @rco-odoo for reviewing. --- addons/crm/data/base_action_rule_demo.xml | 2 - addons/crm/views/crm_lead_views.xml | 1 - addons/hr_contract/data/hr_contract_data.xml | 2 - .../data/hr_timesheet_sheet_data.xml | 1 - addons/lunch/views/lunch_views.xml | 1 - addons/mail/models/ir_actions.py | 5 - addons/mail/views/ir_actions_views.xml | 21 -- .../data/marketing_campaign_crm_demo.xml | 1 - addons/survey/views/survey_views.xml | 1 - addons/website/data/website_demo.xml | 2 - addons/website/views/ir_actions_views.xml | 2 - odoo/addons/base/ir/ir_actions.py | 281 ++---------------- odoo/addons/base/ir/ir_actions.xml | 160 +++------- .../wizard/base_module_immediate_install.xml | 1 - odoo/addons/base/tests/test_ir_actions.py | 106 +------ 15 files changed, 70 insertions(+), 517 deletions(-) diff --git a/addons/crm/data/base_action_rule_demo.xml b/addons/crm/data/base_action_rule_demo.xml index 6307c778996..a4fcca49b46 100644 --- a/addons/crm/data/base_action_rule_demo.xml +++ b/addons/crm/data/base_action_rule_demo.xml @@ -11,7 +11,6 @@ Reminder to User - True ir.actions.server email @@ -38,7 +37,6 @@ Set team to Sales Department - True ir.actions.server code record.write({'team_id': env.ref('sales_team.team_sales_department').id}) diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml index c76e4154f3e..856d88b6fe9 100644 --- a/addons/crm/views/crm_lead_views.xml +++ b/addons/crm/views/crm_lead_views.xml @@ -774,7 +774,6 @@ if record: code action = model.action_your_pipeline() - diff --git a/addons/hr_contract/data/hr_contract_data.xml b/addons/hr_contract/data/hr_contract_data.xml index 699e7160b6b..39ffc30e598 100644 --- a/addons/hr_contract/data/hr_contract_data.xml +++ b/addons/hr_contract/data/hr_contract_data.xml @@ -51,7 +51,6 @@ HR Contract: set as pending - True ir.actions.server code record.set_as_pending() @@ -82,7 +81,6 @@ HR Contract: set as close - True ir.actions.server code record.set_as_close() diff --git a/addons/hr_timesheet_sheet/data/hr_timesheet_sheet_data.xml b/addons/hr_timesheet_sheet/data/hr_timesheet_sheet_data.xml index ccd9b5e2498..e49e3d2cbbb 100644 --- a/addons/hr_timesheet_sheet/data/hr_timesheet_sheet_data.xml +++ b/addons/hr_timesheet_sheet/data/hr_timesheet_sheet_data.xml @@ -8,7 +8,6 @@ ir.actions.server action = model.open_timesheet() - True My Timesheet diff --git a/addons/lunch/views/lunch_views.xml b/addons/lunch/views/lunch_views.xml index 849a3237054..b5358238c49 100644 --- a/addons/lunch/views/lunch_views.xml +++ b/addons/lunch/views/lunch_views.xml @@ -415,7 +415,6 @@ Archive/Restore - True ir.actions.server code diff --git a/addons/mail/models/ir_actions.py b/addons/mail/models/ir_actions.py index 33056302562..8486e00d9a1 100644 --- a/addons/mail/models/ir_actions.py +++ b/addons/mail/models/ir_actions.py @@ -11,11 +11,6 @@ class ServerActions(models.Model): _inherit = ['ir.actions.server'] state = fields.Selection(selection_add=[('email', 'Send Email')]) - email_from = fields.Char('From', related='template_id.email_from', readonly=True) - email_to = fields.Char('To (Emails)', related='template_id.email_to', readonly=True) - partner_to = fields.Char('To (Partners)', related='template_id.partner_to', readonly=True) - subject = fields.Char('Subject', related='template_id.subject', readonly=True) - body_html = fields.Html('Body', related='template_id.body_html', readonly=True) template_id = fields.Many2one( 'mail.template', 'Email Template', ondelete='set null', domain="[('model_id', '=', model_id)]", diff --git a/addons/mail/views/ir_actions_views.xml b/addons/mail/views/ir_actions_views.xml index 2167449417f..8ed68b586d5 100644 --- a/addons/mail/views/ir_actions_views.xml +++ b/addons/mail/views/ir_actions_views.xml @@ -16,27 +16,6 @@ -

- Choose a template to display its values. -

-

- The values displayed hereunder are informative. When sending the email, the values - will be taken from the email template. -

-
- - diff --git a/addons/marketing_campaign_crm_demo/data/marketing_campaign_crm_demo.xml b/addons/marketing_campaign_crm_demo/data/marketing_campaign_crm_demo.xml index b959aefac4d..dc2c70ba513 100644 --- a/addons/marketing_campaign_crm_demo/data/marketing_campaign_crm_demo.xml +++ b/addons/marketing_campaign_crm_demo/data/marketing_campaign_crm_demo.xml @@ -5,7 +5,6 @@ code True -
diff --git a/addons/survey/views/survey_views.xml b/addons/survey/views/survey_views.xml index c84aaa3879c..a810f095637 100644 --- a/addons/survey/views/survey_views.xml +++ b/addons/survey/views/survey_views.xml @@ -635,7 +635,6 @@ Clean test answers - True ir.actions.server code diff --git a/addons/website/data/website_demo.xml b/addons/website/data/website_demo.xml index bd75e56363c..482ad1f1832 100644 --- a/addons/website/data/website_demo.xml +++ b/addons/website/data/website_demo.xml @@ -36,7 +36,6 @@ Website Partner Post and Thanks Demo - True partner_thanks @@ -112,7 +111,6 @@ response = request.website.render("website.template_partner_post", {'partner': p Website Partners Comment Form - True partner_comment diff --git a/addons/website/views/ir_actions_views.xml b/addons/website/views/ir_actions_views.xml index d692ce845cd..9f06b0448f7 100644 --- a/addons/website/views/ir_actions_views.xml +++ b/addons/website/views/ir_actions_views.xml @@ -11,8 +11,6 @@ - - diff --git a/odoo/addons/base/ir/ir_actions.py b/odoo/addons/base/ir/ir_actions.py index 3c458a7c723..92de0cb2f4f 100644 --- a/odoo/addons/base/ir/ir_actions.py +++ b/odoo/addons/base/ir/ir_actions.py @@ -457,23 +457,17 @@ class IrActionsServer(models.Model): name = fields.Char(string='Action Name', translate=True) type = fields.Char(default='ir.actions.server') - - condition = fields.Char(default="True", groups='base.group_system', - help="Condition verified before executing the server action. If it " - "is not verified, the action will not be executed. The condition is " - "a Python expression, like 'record.list_price > 5000'. A void " - "condition is considered as always True. Help about python expression " - "is given in the help tab.") + usage = fields.Selection([ + ('ir_actions_server', 'Server Action')], string='Usage', + default='ir_actions_server', required=True) state = fields.Selection([ ('code', 'Execute Python Code'), - ('client_action', 'Run a Client Action'), - ('object_create', 'Create or Copy a new Record'), - ('object_write', 'Write on a Record'), + ('object_create', 'Create a new Record'), + ('object_write', 'Update the Record'), ('multi', 'Execute several actions')], string='Action To Do', default='code', required=True, # set default to write ? help="Type of server action. The following values are available:\n" "- 'Execute Python Code': a block of python code that will be executed\n" - "- 'Run a Client Action': choose a client action to launch\n" "- 'Create or Copy a new Record': create a new record with new values, or copy an existing record in your database\n" "- 'Write on a Record': update the values of a record\n" "- 'Execute several actions': define an action that triggers several other server actions\n" @@ -488,9 +482,6 @@ class IrActionsServer(models.Model): menu_ir_values_id = fields.Many2one('ir.values', string='Action on Object', copy=False, readonly=True, help='IrValues entry of the related more menu entry action') - # Client Action - action_id = fields.Many2one('ir.actions.actions', string='Client Action', - help="Select the client action that has to be executed.") # Python code code = fields.Text(string='Python Code', groups='base.group_system', default=DEFAULT_PYTHON_CODE, @@ -499,79 +490,15 @@ class IrActionsServer(models.Model): # Multi child_ids = fields.Many2many('ir.actions.server', 'rel_server_actions', 'server_id', 'action_id', string='Child Actions', help='Child server actions that will be executed. Note that the last return returned action value will be used as global return value.') - # Create/Copy/Write - use_create = fields.Selection([('new', 'Create a new record in the Base Model'), - ('new_other', 'Create a new record in another model'), - ('copy_current', 'Copy the current record'), - ('copy_other', 'Choose and copy a record in the database')], - string="Creation Policy", default='new', required=True) + # Create crud_model_id = fields.Many2one('ir.model', string='Create/Write Target Model', oldname='srcmodel_id', help="Model for record creation / update. Set this field only to specify a different model than the base model.") - crud_model_name = fields.Char(string='Create/Write Target Model Name', related='crud_model_id.model', store=True, readonly=True) - ref_object = fields.Reference(string='Reference record', selection='_select_objects', oldname='copy_object') - link_new_record = fields.Boolean(string='Attach the new record', - help="Check this if you want to link the newly-created record " - "to the current record on which the server action runs.") + crud_model_name = fields.Char(related='crud_model_id.name', readonly=True) link_field_id = fields.Many2one('ir.model.fields', string='Link using field', - oldname='record_id', help="Provide the field where the record id is stored after the operations.") - use_write = fields.Selection([('current', 'Update the current record'), - ('expression', 'Update a record linked to the current record using python'), - ('other', 'Choose and Update a record in the database')], - string='Update Policy', default='current', required=True) - write_expression = fields.Char(string='Expression', oldname='write_id', - help="Provide an expression that, applied on the current record, gives the field to update.") + help="Provide the field used to link the newly created record" + "on the record on used by the server action.") fields_lines = fields.One2many('ir.server.object.lines', 'server_id', string='Value Mapping', copy=True) - # Fake fields used to implement the placeholder assistant - model_object_field = fields.Many2one('ir.model.fields', string="Field", - help="Select target field from the related document model.\n" - "If it is a relationship field you will be able to select " - "a target field at the destination of the relationship.") - sub_object = fields.Many2one('ir.model', string='Sub-model', readonly=True, - help="fWhen a relationship field is selected as first field, " - "this field shows the document model the relationship goes to.") - sub_model_object_field = fields.Many2one('ir.model.fields', string='Sub-field', - help="When a relationship field is selected as first field, " - "this field lets you select the target field within the " - "destination document model (sub-model).") - copyvalue = fields.Char(string='Placeholder Expression', help="Final placeholder expression, to be copy-pasted in the desired template field.") - # Fake fields used to implement the ID finding assistant - id_object = fields.Reference(string='Record', selection='_select_objects') - id_value = fields.Char(string='Record ID') - - def _check_expression(self, expression, model): - """ Check python expression (condition, write_expression). Each step of - the path must be a valid many2one field, or an integer field for the last - step. - - :param str expression: a python expression, beginning by 'obj' or 'object' - :param model: a record of the model 'ir.model' - :returns tuple: (is_valid, target_model_name, error_msg) - """ - if not model: - return (False, None, 'Your expression cannot be validated because the Base Model is not set.') - # fetch current model - model_name = model.model - # transform expression into a path that should look like 'object.many2onefield.many2onefield' - path = expression.split('.') - initial = path.pop(0) - if initial not in ['obj', 'object', 'record']: - return (False, None, 'Your expression should begin with obj, object, record.\nAn expression builder is available in the help tab.') - # analyze path - while path: - step = path.pop(0) - field = self.env[model_name]._fields.get(step) - if not field: - return (False, None, 'Part of the expression (%s) is not recognized as a column in the model %s.' % (step, model_name)) - ftype = field.type - if ftype not in ['many2one', 'int']: - return (False, None, 'Part of the expression (%s) is not a valid column type (is %s, should be a many2one or an int)' % (step, ftype)) - if ftype == 'int' and path: - return (False, None, 'Part of the expression (%s) is an integer field that is only allowed at the end of an expression' % (step)) - if ftype == 'many2one': - model_name = field.comodel_name - return (True, model_name, None) - @api.constrains('code') def _check_python_code(self): for action in self.sudo().filtered('code'): @@ -579,142 +506,19 @@ class IrActionsServer(models.Model): if msg: raise ValidationError(msg) - @api.constrains('write_expression', 'model_id') - def _check_write_expression(self): - for record in self: - if record.write_expression and record.model_id: - correct, model_name, message = self._check_expression(record.write_expression, record.model_id) - if not correct: - _logger.warning('Invalid expression: %s' % message) - raise ValidationError(_('Incorrect Write Record Expression')) - @api.constrains('child_ids') def _check_recursion(self): if not self._check_m2m_recursion('child_ids'): raise ValidationError(_('Recursion found in child server actions')) - @api.onchange('model_id') - def _onchange_model_id(self): - """ When changing the action base model, reset crud config - to ease value coherence. """ - self.use_create = 'new' - self.use_write = 'current' - self.crud_model_id = self.model_id - - @api.onchange('use_create', 'use_write', 'ref_object') - def _onchange_crud_config(self): - """ Wrapper on CRUD-type (create or write) on_change """ - if self.state == 'object_create': - self._onchange_create_config() - elif self.state == 'object_write': - self._onchange_write_config() - - def _onchange_create_config(self): - """ When changing the object_create type configuration: - - - `new` and `copy_current`: crud_model_id is the same as base model - - `new_other`: user choose crud_model_id - - `copy_other`: disassemble the reference object to have its model - - if the target model has changed, then reset the link field that is - probably not correct anymore - """ - crud_model_id = self.crud_model_id - - if self.use_create == 'new': - self.crud_model_id = self.model_id - elif self.use_create == 'new_other': - pass - elif self.use_create == 'copy_current': - self.crud_model_id = self.model_id - elif self.use_create == 'copy_other' and self.ref_object: - ref_model = self.ref_object._name - self.crud_model_id = self.env['ir.model'].search([('model', '=', ref_model)]) - - if self.crud_model_id != crud_model_id: - self.link_field_id = False - - def _onchange_write_config(self): - """ When changing the object_write type configuration: - - - `current`: crud_model_id is the same as base model - - `other`: disassemble the reference object to have its model - - `expression`: has its own on_change, nothing special here - """ - crud_model_id = self.crud_model_id - - if self.use_write == 'current': - self.crud_model_id = self.model_id - elif self.use_write == 'other' and self.ref_object: - ref_model = self.ref_object._name - self.crud_model_id = self.env['ir.model'].search([('model', '=', ref_model)]) - elif self.use_write == 'expression': - pass - - if self.crud_model_id != crud_model_id: - self.link_field_id = False - @api.onchange('crud_model_id') def _onchange_crud_model_id(self): - """ When changing the CRUD model, update its stored name also """ self.link_field_id = False + self.crud_model_name = self.crud_model_id.model - @api.onchange('write_expression') - def _onchange_write_expression(self): - """ Check the write_expression and update crud_model_id accordingly """ - values = {} - if self.write_expression: - valid, model_name, message = self._check_expression(self.write_expression, self.model_id) - else: - valid, model_name, message = True, None, False - if self.model_id: - model_name = self.model_id.model - if not valid: - return { - 'warning': { - 'title': _('Incorrect expression'), - 'message': message or _('Invalid expression'), - } - } - if model_name: - self.crud_model_id = self.env['ir.model'].search([('model', '=', model_name)]) - - def _build_expression(self, field_name, sub_field_name): - """ Returns a placeholder expression for use in a template field, - based on the values provided in the placeholder assistant. - - :param field_name: main field name - :param sub_field_name: sub field name (M2O) - :return: final placeholder expression - """ - expression = '' - if field_name: - expression = "object." + field_name - if sub_field_name: - expression += "." + sub_field_name - return expression - - @api.onchange('model_object_field', 'sub_model_object_field') - def _onchange_model_object_field(self): - field = self.model_object_field - sub_field = self.sub_model_object_field - - self.sub_object = False - self.sub_model_object_field = False - self.copyvalue = False - - if field: - if field.ttype in ['many2one', 'one2many', 'many2many']: - comodel = self.env['ir.model'].search([('model', '=', field.relation)]) - if comodel: - self.sub_object = comodel - self.copyvalue = self._build_expression(field.name, sub_field.name) - self.sub_model_object_field = sub_field - else: - self.copyvalue = self._build_expression(field.name, False) - - @api.onchange('id_object') - def _onchange_id_object(self): - self.id_value = self.id_object.id if self.id_object else False + @api.onchange('model_id') + def _onchange_model_id(self): + self.model_name = self.model_id.model @api.multi def create_action(self): @@ -741,13 +545,6 @@ class IrActionsServer(models.Model): raise UserError(_('Deletion of the action record failed.')) return True - @api.model - def run_action_client_action(self, action, eval_context=None): - if not action.action_id: - raise UserError(_("Please specify an action to launch!")) - record = self.env[action.action_id.type].browse(action.action_id.id) - return record.read()[0] - @api.model 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' @@ -778,21 +575,7 @@ class IrActionsServer(models.Model): for exp in action.fields_lines: res[exp.col1.name] = exp.eval_value(eval_context=eval_context)[exp.id] - if action.use_write == 'current': - model = action.model_id.model - ref_id = self._context.get('active_id') - elif action.use_write == 'other': - model = action.crud_model_id.model - ref_id = action.ref_object.id - elif action.use_write == 'expression': - model = action.crud_model_id.model - ref = safe_eval(action.write_expression, eval_context) - if isinstance(ref, models.BaseModel): - ref_id = ref.id - else: - ref_id = int(ref) - - self.env[model].browse(ref_id).write(res) + 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): @@ -811,27 +594,16 @@ class IrActionsServer(models.Model): for exp in action.fields_lines: res[exp.col1.name] = exp.eval_value(eval_context=eval_context)[exp.id] - if action.use_create in ['new', 'copy_current']: - model = action.model_id.model - elif action.use_create in ['new_other', 'copy_other']: - model = action.crud_model_id.model + res = self.env[action.crud_model_id.model].create(res) - if action.use_create == 'copy_current': - ref_id = self._context.get('active_id') - res = self.env[model].browse(ref_id).copy(res) - elif action.use_create == 'copy_other': - res = action.ref_object.copy(res) - else: - res = self.env[model].create(res) - - if action.link_new_record and action.link_field_id: + if action.link_field_id: record = self.env[action.model_id.model].browse(self._context.get('active_id')) 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 - condition or code server actions. + python formulas or code server actions. :param action: the current server action :type action: browse record @@ -868,10 +640,9 @@ class IrActionsServer(models.Model): @api.multi def run(self): - """ Runs the server action. For each server action, the condition is - checked. Note that a void (``False``) condition is considered as always - valid. If it is verified, the run_action_ method is called. This - allows easy overriding of the server actions. + """ Runs the server action. For each server action, the + run_action_ method is called. This allows easy overriding + of the server actions. :param dict context: context should contain following keys @@ -888,14 +659,7 @@ class IrActionsServer(models.Model): """ for action in self: eval_context = self._get_eval_context(action) - condition = action.sudo().condition - if condition is False: - # Void (aka False) conditions are considered as True - condition = True if hasattr(self, 'run_action_%s_multi' % action.state): - expr = safe_eval(str(condition), eval_context) - if not expr: - continue # call the multi method run_self = self.with_context(eval_context['env'].context) func = getattr(run_self, 'run_action_%s_multi' % action.state) @@ -908,9 +672,6 @@ class IrActionsServer(models.Model): # 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 - expr = safe_eval(str(condition), eval_context) - if not expr: - continue # 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) @@ -934,7 +695,7 @@ class IrServerObjectLines(models.Model): col1 = fields.Many2one('ir.model.fields', string='Field', required=True) value = fields.Text(required=True, help="Expression containing a value specification. \n" "When Formula type is selected, this field may be a Python expression " - " that can use the same values as for the condition field on the server action.\n" + " that can use the same values as for the code field on the server action.\n" "If Value type is selected, the value will be used directly without evaluation.") type = fields.Selection([('value', 'Value'), ('equation', 'Python expression')], 'Evaluation Type', default='value', required=True, change_default=True) diff --git a/odoo/addons/base/ir/ir_actions.xml b/odoo/addons/base/ir/ir_actions.xml index 488434c5333..3bf07f1c798 100644 --- a/odoo/addons/base/ir/ir_actions.xml +++ b/odoo/addons/base/ir/ir_actions.xml @@ -262,128 +262,66 @@ ir.actions.server
+
+ +
-
- -
- - - + + + - - - - + + + + - + - - - - - - - -

- Please set the Base Model before setting the action details. + Please set the Model to Create before choosing values

- - - - - - + + + + + + +
-

If several child actions return an action, only the last one will be executed. This may happen when having server actions executing code that returns an action, or server actions returning a client action.

- +
- +

Help with Python expressions.

@@ -394,13 +332,6 @@
  • time, datetime, timezone: Python module
  • log(message, level='info'): a logging function to create ir_logging records
  • -
    -

    Example of condition expression using Python

    -
      -
    • condition: True
    • -
    • condition: record.list_price > 5000
    • -
    -

    Example of python code

    @@ -409,25 +340,6 @@ env["res.partner"].create({"name": partner_name})
    - -

    Dynamic expression builder

    -

    - Please set the Base Model of the action to enable the dynamic expression buidler. -

    - - - - -

    Find the ID of a record in the database

    - - -
    @@ -440,9 +352,11 @@ env["res.partner"].create({"name": partner_name}) ir.actions.server + +
    @@ -457,6 +371,7 @@ env["res.partner"].create({"name": partner_name}) +
    @@ -540,7 +455,6 @@ env["res.partner"].create({"name": partner_name}) Run Remaining Action Todo - True ir.actions.server code diff --git a/odoo/addons/base/module/wizard/base_module_immediate_install.xml b/odoo/addons/base/module/wizard/base_module_immediate_install.xml index 70a79f26c31..c9e0edc1b12 100644 --- a/odoo/addons/base/module/wizard/base_module_immediate_install.xml +++ b/odoo/addons/base/module/wizard/base_module_immediate_install.xml @@ -3,7 +3,6 @@ Module Immediate Install - True ir.actions.server code diff --git a/odoo/addons/base/tests/test_ir_actions.py b/odoo/addons/base/tests/test_ir_actions.py index 0d5174b179c..74d220b05bb 100644 --- a/odoo/addons/base/tests/test_ir_actions.py +++ b/odoo/addons/base/tests/test_ir_actions.py @@ -42,7 +42,6 @@ class TestServerActionsBase(common.TransactionCase): # create server action to self.action = self.env['ir.actions.server'].create({ 'name': 'TestAction', - 'condition': 'True', 'model_id': self.res_partner_model.id, 'state': 'code', 'code': 'record.write({"comment": "MyComment"})', @@ -52,16 +51,10 @@ class TestServerActionsBase(common.TransactionCase): class TestServerActions(TestServerActionsBase): def test_00_action(self): - # Do: eval 'True' condition self.action.with_context(self.context).run() self.assertEqual(self.test_partner.comment, 'MyComment', 'ir_actions_server: invalid condition check') self.test_partner.write({'comment': False}) - # Do: eval False condition, that should be considered as True (void = True) - self.action.write({'condition': False}) - self.action.with_context(self.context).run() - self.assertEqual(self.test_partner.comment, 'MyComment', 'ir_actions_server: invalid condition check') - # Do: create contextual action self.action.create_action() @@ -90,27 +83,14 @@ class TestServerActions(TestServerActionsBase): partners = self.test_partner.search([('name', 'ilike', 'TestingPartner_code')]) self.assertEqual(len(partners), 1, 'ir_actions_server: 1 new partner should have been created') - def test_30_client(self): - client_action = self.env['ir.actions.client'].create({ - 'name': 'TestAction2', - 'tag': 'Test', - }) - self.action.write({ - 'state': 'client_action', - 'action_id': client_action.id, - }) - res = self.action.with_context(self.context).run() - self.assertEqual(res['name'], 'TestAction2', 'ir_actions_server: incorrect return result for a client action') - - def test_40_crud_create(self): + def test_20_crud_create(self): _city = 'TestCity' _name = 'TestNew' # Do: create a new record in the same model and link it self.action.write({ 'state': 'object_create', - 'use_create': 'new', - 'link_new_record': True, + 'crud_model_id': self.action.model_id.id, 'link_field_id': self.res_partner_parent_field.id, 'fields_lines': [(0, 0, {'col1': self.res_partner_name_field.id, 'value': _name}), (0, 0, {'col1': self.res_partner_city_field.id, 'value': _city})], @@ -124,29 +104,11 @@ class TestServerActions(TestServerActionsBase): # Test: new partner linked self.assertEqual(self.test_partner.parent_id, partner, 'ir_actions_server: TODO') - # Do: copy current record - self.action.write({ - 'state': 'object_create', - 'use_create': 'copy_current', - 'link_new_record': False, - 'fields_lines': [(5,), - (0, 0, {'col1': self.res_partner_name_field.id, 'value': 'TestCopyCurrent'}), - (0, 0, {'col1': self.res_partner_city_field.id, 'value': 'TestCity'})], - }) - run_res = self.action.with_context(self.context).run() - self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False') - # Test: new partner created - partner = self.test_partner.search([('name', 'ilike', 'TestingPartner (copy)')]) # currently res_partner overrides default['name'] whatever its value - self.assertEqual(len(partner), 1, 'ir_actions_server: TODO') - self.assertEqual(partner.city, 'TestCity', 'ir_actions_server: TODO') - self.assertEqual(partner.country_id, self.test_partner.country_id, 'ir_actions_server: TODO') - # Do: create a new record in another model self.action.write({ 'state': 'object_create', - 'use_create': 'new_other', 'crud_model_id': self.res_country_model.id, - 'link_new_record': False, + 'link_field_id': False, 'fields_lines': [(5,), (0, 0, {'col1': self.res_country_name_field.id, 'value': 'record.name', 'type': 'equation'}), (0, 0, {'col1': self.res_country_code_field.id, 'value': 'record.name[0:2]', 'type': 'equation'})], @@ -158,69 +120,23 @@ class TestServerActions(TestServerActionsBase): self.assertEqual(len(country), 1, 'ir_actions_server: TODO') self.assertEqual(country.code, 'TE', 'ir_actions_server: TODO') - # Do: copy a record in another model - self.action.write({ - 'state': 'object_create', - 'use_create': 'copy_other', - 'crud_model_id': self.res_country_model.id, - 'link_new_record': False, - 'ref_object': 'res.country,%s' % self.test_country.id, - 'fields_lines': [(5,), - (0, 0, {'col1': self.res_country_name_field.id, 'value': 'NewCountry', 'type': 'value'}), - (0, 0, {'col1': self.res_country_code_field.id, 'value': 'NY', 'type': 'value'})], - }) - run_res = self.action.with_context(self.context).run() - self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False') - # Test: new country created - country = self.test_country.search([('name', 'ilike', 'NewCountry')]) - self.assertEqual(len(country), 1, 'ir_actions_server: TODO') - self.assertEqual(country.code, 'NY', 'ir_actions_server: TODO') - self.assertEqual(country.address_format, 'SuperFormat', 'ir_actions_server: TODO') - - def test_50_crud_write(self): + def test_30_crud_write(self): _name = 'TestNew' - # Do: create a new record in the same model and link it + # Do: update partner name self.action.write({ 'state': 'object_write', - 'use_write': 'current', 'fields_lines': [(0, 0, {'col1': self.res_partner_name_field.id, 'value': _name})], }) run_res = self.action.with_context(self.context).run() self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False') - # Test: new partner created + # Test: partner updated partner = self.test_partner.search([('name', 'ilike', _name)]) self.assertEqual(len(partner), 1, 'ir_actions_server: TODO') self.assertEqual(partner.city, 'OrigCity', 'ir_actions_server: TODO') - # Do: copy current record - self.action.write({ - 'use_write': 'other', - 'crud_model_id': self.res_country_model.id, - 'ref_object': 'res.country,%s' % self.test_country.id, - 'fields_lines': [(5,), (0, 0, {'col1': self.res_country_name_field.id, 'value': 'record.name', 'type': 'equation'})], - }) - run_res = self.action.with_context(self.context).run() - self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False') - # Test: new country created - country = self.test_country.search([('name', 'ilike', 'TestNew')]) - self.assertEqual(len(country), 1, 'ir_actions_server: TODO') - - # Do: copy a record in another model - self.action.write({ - 'use_write': 'expression', - 'crud_model_id': self.res_country_model.id, - 'write_expression': 'record.country_id', - 'fields_lines': [(5,), (0, 0, {'col1': self.res_country_name_field.id, 'value': 'NewCountry', 'type': 'value'})], - }) - run_res = self.action.with_context(self.context).run() - self.assertFalse(run_res, 'ir_actions_server: create record action correctly finished should return False') - # Test: new country created - country = self.test_country.search([('name', 'ilike', 'NewCountry')]) - self.assertEqual(len(country), 1, 'ir_actions_server: TODO') - @mute_logger('odoo.addons.base.ir.ir_model', 'odoo.models') - def test_60_multi(self): + def test_40_multi(self): # Data: 2 server actions that will be nested action1 = self.action.create({ 'name': 'Subaction1', @@ -233,8 +149,10 @@ class TestServerActions(TestServerActionsBase): 'name': 'Subaction2', 'sequence': 2, 'model_id': self.res_partner_model.id, + 'crud_model_id': self.res_partner_model.id, 'state': 'object_create', - 'use_create': 'copy_current', + 'fields_lines': [(0, 0, {'col1': self.res_partner_name_field.id, 'value': 'RaoulettePoiluchette'}), + (0, 0, {'col1': self.res_partner_city_field.id, 'value': 'TestingCity'})], }) action3 = self.action.create({ 'name': 'Subaction3', @@ -253,8 +171,8 @@ class TestServerActions(TestServerActionsBase): # Test: new partner created # currently res_partner overrides default['name'] whatever its value - partner = self.test_partner.search([('name', 'ilike', 'TestingPartner (copy)')]) - self.assertEqual(len(partner), 1, 'ir_actions_server: TODO') + partner = self.test_partner.search([('name', 'ilike', 'RaoulettePoiluchette')]) + self.assertEqual(len(partner), 1) # Test: action returned self.assertEqual(res.get('type'), 'ir.actions.act_url') From 870974effeb77f1125394816aa10b96f7f1b1941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 14 Dec 2016 17:57:33 +0100 Subject: [PATCH 04/11] [IMP] ir_actions, mail: add add_followers server action Add a server action allowing to add followers, either partners or channels, to documents. --- addons/mail/models/ir_actions.py | 17 ++++++++++++++++- addons/mail/tests/test_ir_actions.py | 14 +++++++++++++- addons/mail/views/ir_actions_views.xml | 21 +++++++++------------ odoo/addons/base/ir/ir_actions.py | 1 + 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/addons/mail/models/ir_actions.py b/addons/mail/models/ir_actions.py index 8486e00d9a1..12c3ae85172 100644 --- a/addons/mail/models/ir_actions.py +++ b/addons/mail/models/ir_actions.py @@ -10,7 +10,14 @@ class ServerActions(models.Model): _name = 'ir.actions.server' _inherit = ['ir.actions.server'] - state = fields.Selection(selection_add=[('email', 'Send Email')]) + state = fields.Selection(selection_add=[ + ('email', 'Send Email'), + ('followers', 'Add Followers') + ]) + # Followers + partner_ids = fields.Many2many('res.partner', string='Add Followers') + channel_ids = fields.Many2many('mail.channel', string='Add Channels') + # Template template_id = fields.Many2one( 'mail.template', 'Email Template', ondelete='set null', domain="[('model_id', '=', model_id)]", @@ -22,6 +29,14 @@ class ServerActions(models.Model): if self.template_id and not self.template_id.email_from: raise UserError(_('Your template should define email_from')) + @api.model + def run_action_followers_multi(self, action, eval_context=None): + Model = self.env[action.model_id.model] + 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'))) + records.message_subscribe(self.partner_ids.ids, self.channel_ids.ids, force=False) + return False + @api.model def run_action_email(self, action, eval_context=None): # TDE CLEANME: when going to new api with server action, remove action diff --git a/addons/mail/tests/test_ir_actions.py b/addons/mail/tests/test_ir_actions.py index 5c482c1aacb..074f603a43f 100644 --- a/addons/mail/tests/test_ir_actions.py +++ b/addons/mail/tests/test_ir_actions.py @@ -6,7 +6,7 @@ from odoo.addons.base.tests.test_ir_actions import TestServerActionsBase class TestServerActionsEmail(TestServerActionsBase): - def test_00_state_email(self): + def test_email_action(self): """ Test ir.actions.server email type """ # create email_template email_template = self.env['mail.template'].create({ @@ -27,3 +27,15 @@ class TestServerActionsEmail(TestServerActionsBase): # check email content self.assertEqual(mail.body, '

    Dear TestingPartner, your parent is False

    ', 'ir_actions_server: TODO') + + def test_followers_action(self): + """ Test ir.actions.server email type """ + self.test_partner.message_unsubscribe(self.test_partner.message_partner_ids.ids) + self.action.write({ + 'state': 'followers', + 'partner_ids': [(4, self.env.ref('base.partner_root').id), (4, self.env.ref('base.partner_demo').id)], + 'channel_ids': [(4, self.env.ref('mail.channel_all_employees').id)] + }) + self.action.with_context(self.context).run() + self.assertEqual(self.test_partner.message_partner_ids, self.env.ref('base.partner_root') | self.env.ref('base.partner_demo')) + self.assertEqual(self.test_partner.message_channel_ids, self.env.ref('mail.channel_all_employees')) diff --git a/addons/mail/views/ir_actions_views.xml b/addons/mail/views/ir_actions_views.xml index 8ed68b586d5..381310bf69c 100644 --- a/addons/mail/views/ir_actions_views.xml +++ b/addons/mail/views/ir_actions_views.xml @@ -7,18 +7,15 @@ ir.actions.server - - -

    - Please set the Base Model before setting the action details. -

    - - - -
    -
    + + + + +
    diff --git a/odoo/addons/base/ir/ir_actions.py b/odoo/addons/base/ir/ir_actions.py index 92de0cb2f4f..7c4b14f898d 100644 --- a/odoo/addons/base/ir/ir_actions.py +++ b/odoo/addons/base/ir/ir_actions.py @@ -471,6 +471,7 @@ class IrActionsServer(models.Model): "- 'Create or Copy a new Record': create a new record with new values, or copy an existing record in your database\n" "- 'Write on a Record': update the values of a record\n" "- 'Execute several actions': define an action that triggers several other server actions\n" + "- 'Add Followers': add followers to a record (available in Discuss)\n" "- 'Send Email': automatically send an email (available in email_template)") # Generic sequence = fields.Integer(default=5, From 1c79c5ee3050ad8ba907aeda70c0d1f3dbe837a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 14 Dec 2016 13:34:48 +0100 Subject: [PATCH 05/11] [REF] ir_cron: make scheduled actions inherits from server actions The purpose of this commit is to handle code execution only in server action and delegate schedule management to ir_cron model. ir.cron model now inherits from ir.actions.server. Fields model, function and args are removed as well as the logic to handle them. There is no more code manipulation and evaluation in ir_cron, only a call to the run method of ir.actions.server. Cron form view use server action form view as primary view. This way automated actions use the same base form as server actions with cron details added. Thanks to @fpodoo for the original idea and preliminary work. Thanks to @jpr-odoo for first developments. Thanks to @jem-odoo for reviewing. --- .../account_asset/data/account_asset_data.xml | 10 +- .../data/base_action_rule_data.xml | 12 +-- .../models/base_action_rule.py | 2 +- addons/base_gengo/data/ir_cron_data.xml | 18 ++-- addons/calendar/data/calendar_cron.xml | 8 +- addons/event/data/event_data.xml | 9 +- addons/fetchmail/data/fetchmail_data.xml | 8 +- addons/fleet/data/fleet_data.xml | 9 +- addons/gamification/data/cron.xml | 11 +-- .../data/google_calendar_data.xml | 8 +- addons/mail/data/mail_data.xml | 17 ++-- addons/maintenance/data/maintenance_cron.xml | 8 +- .../data/marketing_campaign_data.xml | 13 ++- .../mass_mailing/views/mass_mailing_views.xml | 11 +-- addons/membership/data/membership_data.xml | 7 +- addons/pos_cache/data/pos_cache_data.xml | 8 +- addons/procurement/data/procurement_data.xml | 10 +- addons/rating_project/data/project_data.xml | 7 +- .../data/website_twitter_data.xml | 22 ++--- odoo/addons/base/ir/ir_actions.py | 3 +- odoo/addons/base/ir/ir_autovacuum.xml | 19 ++-- odoo/addons/base/ir/ir_cron.py | 96 +++++++------------ odoo/addons/base/ir/ir_cron_view.xml | 52 ++++------ odoo/addons/base/tests/test_search.py | 2 +- 24 files changed, 159 insertions(+), 211 deletions(-) diff --git a/addons/account_asset/data/account_asset_data.xml b/addons/account_asset/data/account_asset_data.xml index f858fbb2237..3f9a28987df 100644 --- a/addons/account_asset/data/account_asset_data.xml +++ b/addons/account_asset/data/account_asset_data.xml @@ -1,13 +1,13 @@ - - Generate Asset Entries + + Account Asset: Generate asset entries + + code + model._cron_generate_entries() 1 months -1 - - - \ No newline at end of file diff --git a/addons/base_action_rule/data/base_action_rule_data.xml b/addons/base_action_rule/data/base_action_rule_data.xml index afcc01447a4..3e3604ead62 100644 --- a/addons/base_action_rule/data/base_action_rule_data.xml +++ b/addons/base_action_rule/data/base_action_rule_data.xml @@ -1,18 +1,16 @@ - - - Check Action Rules + + Base Action Rule: check and execute + + code + model._check(True) 4 hours -1 - - - - diff --git a/addons/base_action_rule/models/base_action_rule.py b/addons/base_action_rule/models/base_action_rule.py index 7f13bf9f3ef..84100a1cc1e 100644 --- a/addons/base_action_rule/models/base_action_rule.py +++ b/addons/base_action_rule/models/base_action_rule.py @@ -119,7 +119,7 @@ class BaseActionRule(models.Model): """ Activate the cron job depending on whether there exists action rules based on time conditions. """ - cron = self.env.ref('base_action_rule.ir_cron_crm_action', raise_if_not_found=False) + cron = self.env.ref('base_action_rule.ir_cron_data_bar_check', raise_if_not_found=False) return cron and cron.toggle(model=self._name, domain=[('kind', '=', 'on_time')]) def _update_registry(self): diff --git a/addons/base_gengo/data/ir_cron_data.xml b/addons/base_gengo/data/ir_cron_data.xml index a88cc78910e..20d62c5db57 100644 --- a/addons/base_gengo/data/ir_cron_data.xml +++ b/addons/base_gengo/data/ir_cron_data.xml @@ -3,28 +3,26 @@ - Gengo Sync Translation (Response) - + Gengo: Sync translation (Response) + + code + model._sync_response(20) 6 hours -1 - - - - Gengo Sync Translation (Request) - + Gengo: Sync translation (Request) + + code + model._sync_request(20) 6 hours -1 - - - diff --git a/addons/calendar/data/calendar_cron.xml b/addons/calendar/data/calendar_cron.xml index 09cda419a54..f195dce4d1e 100644 --- a/addons/calendar/data/calendar_cron.xml +++ b/addons/calendar/data/calendar_cron.xml @@ -3,16 +3,16 @@ - Run Event Reminder + Calendar: Event Reminder + + code + model.get_next_mail() 30 minutes -1 - - - \ No newline at end of file diff --git a/addons/event/data/event_data.xml b/addons/event/data/event_data.xml index 3232b5514a4..b4c1070bf0d 100644 --- a/addons/event/data/event_data.xml +++ b/addons/event/data/event_data.xml @@ -13,16 +13,15 @@ - [event] mail scheduler - True + Event: Mail Scheduler + + code + model.run(True) 1 hours -1 - - event.mail - run
    diff --git a/addons/fetchmail/data/fetchmail_data.xml b/addons/fetchmail/data/fetchmail_data.xml index 34469793c6e..d98c3d1f92f 100644 --- a/addons/fetchmail/data/fetchmail_data.xml +++ b/addons/fetchmail/data/fetchmail_data.xml @@ -2,14 +2,14 @@ - Fetchmail Service + Mail: Fetchmail Service + + code + model._fetch_mails() 5 minutes -1 - fetchmail.server - _fetch_mails - () diff --git a/addons/fleet/data/fleet_data.xml b/addons/fleet/data/fleet_data.xml index 0d9a4640f2b..3ba311c878e 100644 --- a/addons/fleet/data/fleet_data.xml +++ b/addons/fleet/data/fleet_data.xml @@ -1,16 +1,15 @@ - Generation of contracts costs based on the costs frequency - + Fleet: Generate contracts costs based on costs frequency + + code + model.run_scheduler() 1 days -1 - - - diff --git a/addons/gamification/data/cron.xml b/addons/gamification/data/cron.xml index 6e273ef4405..0ed59d39e11 100644 --- a/addons/gamification/data/cron.xml +++ b/addons/gamification/data/cron.xml @@ -1,15 +1,14 @@ - - Run Goal Challenge Checker + + Gamification: Goal Challenge Check + + code + model._cron_update() 1 days -1 - gamification.challenge - _cron_update - () diff --git a/addons/google_calendar/data/google_calendar_data.xml b/addons/google_calendar/data/google_calendar_data.xml index 3fc26e25351..42b583cf7da 100644 --- a/addons/google_calendar/data/google_calendar_data.xml +++ b/addons/google_calendar/data/google_calendar_data.xml @@ -2,15 +2,15 @@ - Calendar synchronization - + Google Calendar: synchronization + + code + model.synchronize_events_cron() 12 hours -1 - - \ No newline at end of file diff --git a/addons/mail/data/mail_data.xml b/addons/mail/data/mail_data.xml index aa4cae02f30..6db20b0f592 100644 --- a/addons/mail/data/mail_data.xml +++ b/addons/mail/data/mail_data.xml @@ -2,29 +2,28 @@ - Email Queue Manager + Mail: Email Queue Manager + + code + model.process_email_queue() 1 hours -1 - - - - Update Notification - + Publisher: Update Notification + + code + model.update_notification(None) 1 weeks -1 - - - 1000 diff --git a/addons/maintenance/data/maintenance_cron.xml b/addons/maintenance/data/maintenance_cron.xml index 141ae9757c3..4345cca6216 100644 --- a/addons/maintenance/data/maintenance_cron.xml +++ b/addons/maintenance/data/maintenance_cron.xml @@ -1,13 +1,13 @@ - Generate preventive maintenance requests + Maintenance: generate preventive maintenance requests + + code + model._cron_generate_requests() 1 days -1 - - - \ No newline at end of file diff --git a/addons/marketing_campaign/data/marketing_campaign_data.xml b/addons/marketing_campaign/data/marketing_campaign_data.xml index 986b58508c9..6e92b7ab1e7 100644 --- a/addons/marketing_campaign/data/marketing_campaign_data.xml +++ b/addons/marketing_campaign/data/marketing_campaign_data.xml @@ -3,27 +3,26 @@ - Marketing Campaign: Campaign workitems processing + + code + model.process_all() 1 hours -1 - - - Marketing Campaign: Segment Sync + + code + model.process_segment() 1 days -1 - - - diff --git a/addons/mass_mailing/views/mass_mailing_views.xml b/addons/mass_mailing/views/mass_mailing_views.xml index 3c15424e9f7..36748205c52 100644 --- a/addons/mass_mailing/views/mass_mailing_views.xml +++ b/addons/mass_mailing/views/mass_mailing_views.xml @@ -794,17 +794,16 @@ - - Process Mass Mailing Queue - + + Mass Mailing: Process queue + + code + model._process_mass_mailing_queue() 60 minutes -1 - mail.mass_mailing - _process_mass_mailing_queue - () - Check membership payment + Membership: update memberships + + code + model._cron_update_membership() days -1 - res.partner - _cron_update_membership diff --git a/addons/pos_cache/data/pos_cache_data.xml b/addons/pos_cache/data/pos_cache_data.xml index 709e6edea35..8bb58b41a4a 100644 --- a/addons/pos_cache/data/pos_cache_data.xml +++ b/addons/pos_cache/data/pos_cache_data.xml @@ -2,15 +2,15 @@ - Refresh POS Cache + PoS: refresh cache + + code + model.refresh_all_caches() 1 hours -1 - - - diff --git a/addons/procurement/data/procurement_data.xml b/addons/procurement/data/procurement_data.xml index 9003b860efd..3538b404f7d 100644 --- a/addons/procurement/data/procurement_data.xml +++ b/addons/procurement/data/procurement_data.xml @@ -1,18 +1,18 @@ - + - Run mrp scheduler + Procurement: run MRP scheduler + + code + model.run_scheduler(True) 1 days -1 - - - diff --git a/addons/rating_project/data/project_data.xml b/addons/rating_project/data/project_data.xml index d60e24ef33c..3a6146b229c 100644 --- a/addons/rating_project/data/project_data.xml +++ b/addons/rating_project/data/project_data.xml @@ -2,11 +2,12 @@ - Send Customer Rating Forms + Project: Send rating + + code + model._send_rating_all() days -1 - project.project - _send_rating_all diff --git a/addons/website_twitter/data/website_twitter_data.xml b/addons/website_twitter/data/website_twitter_data.xml index 470fdddc6a2..fca63620cda 100644 --- a/addons/website_twitter/data/website_twitter_data.xml +++ b/addons/website_twitter/data/website_twitter_data.xml @@ -1,16 +1,14 @@ - - - Fetch new Twitter favorites - 2 - hours - -1 - - website - _refresh_favorite_tweets - () - - + + Twitter: Fetch new favorites + + code + model._refresh_favorite_tweets() + 2 + hours + -1 + + diff --git a/odoo/addons/base/ir/ir_actions.py b/odoo/addons/base/ir/ir_actions.py index 7c4b14f898d..d66abd39c67 100644 --- a/odoo/addons/base/ir/ir_actions.py +++ b/odoo/addons/base/ir/ir_actions.py @@ -458,7 +458,8 @@ class IrActionsServer(models.Model): name = fields.Char(string='Action Name', translate=True) type = fields.Char(default='ir.actions.server') usage = fields.Selection([ - ('ir_actions_server', 'Server Action')], string='Usage', + ('ir_actions_server', 'Server Action'), + ('ir_cron', 'Scheduled Action')], string='Usage', default='ir_actions_server', required=True) state = fields.Selection([ ('code', 'Execute Python Code'), diff --git a/odoo/addons/base/ir/ir_autovacuum.xml b/odoo/addons/base/ir/ir_autovacuum.xml index 1e015c89395..52e07855b63 100644 --- a/odoo/addons/base/ir/ir_autovacuum.xml +++ b/odoo/addons/base/ir/ir_autovacuum.xml @@ -1,13 +1,12 @@ - - Auto-vacuum internal data - 1 - days - -1 - True - ir.autovacuum - power_on - () - + + Base: Auto-vacuum internal data + + code + model.power_on() + 1 + days + -1 + diff --git a/odoo/addons/base/ir/ir_cron.py b/odoo/addons/base/ir/ir_cron.py index 12927e93bdb..cf8f7d97a26 100644 --- a/odoo/addons/base/ir/ir_cron.py +++ b/odoo/addons/base/ir/ir_cron.py @@ -10,17 +10,13 @@ from dateutil.relativedelta import relativedelta import odoo from odoo import api, fields, models, _ -from odoo.exceptions import UserError, ValidationError -from odoo.tools.safe_eval import safe_eval +from odoo.exceptions import UserError _logger = logging.getLogger(__name__) BASE_VERSION = odoo.modules.load_information_from_description_file('base')['version'] -def str2tuple(s): - return safe_eval('tuple(%s)' % (s or '')) - _intervalTypes = { 'days': lambda interval: relativedelta(days=interval), 'hours': lambda interval: relativedelta(hours=interval), @@ -40,9 +36,12 @@ class ir_cron(models.Model): # See also odoo.cron _name = "ir.cron" - _order = 'name' + _order = 'cron_name' - name = fields.Char(required=True) + ir_actions_server_id = fields.Many2one( + 'ir.actions.server', 'Server action', + delegate=True, ondelete='restrict', required=True) + cron_name = fields.Char('Name', related='ir_actions_server_id.name', store=True) user_id = fields.Many2one('res.users', string='User', default=lambda self: self.env.user, required=True) active = fields.Boolean(default=True) interval_number = fields.Integer(default=1, help="Repeat every x.") @@ -54,77 +53,50 @@ class ir_cron(models.Model): numbercall = fields.Integer(string='Number of Calls', default=1, help='How many times the method is called,\na negative number indicates no limit.') doall = fields.Boolean(string='Repeat Missed', help="Specify if missed occurrences should be executed when the server restarts.") nextcall = fields.Datetime(string='Next Execution Date', required=True, default=fields.Datetime.now, help="Next planned execution date for this job.") - model = fields.Char(string='Object', help="Model name on which the method to be called is located, e.g. 'res.partner'.") - function = fields.Char(string='Method', help="Name of the method to be called when this job is processed.") - args = fields.Text(string='Arguments', help="Arguments to be passed to the method, e.g. (uid,).") priority = fields.Integer(default=5, help='The priority of the job, as an integer: 0 means higher priority, 10 means lower priority.') - @api.constrains('args') - def _check_args(self): - try: - for this in self: - str2tuple(this.args) - except Exception: - raise ValidationError(_('Invalid arguments')) + @api.model + def create(self, values): + values['usage'] = 'ir_cron' + return super(ir_cron, self).create(values) @api.multi def method_direct_trigger(self): for cron in self: - self.sudo(user=cron.user_id.id)._callback(cron.model, cron.function, cron.args, cron.id) + self.sudo(user=cron.user_id.id).ir_actions_server_id.run() return True @api.model - def _handle_callback_exception(self, model_name, method_name, args, job_id, job_exception): + def _handle_callback_exception(self, cron_name, server_action_id, job_id, job_exception): """ Method called when an exception is raised by a job. - Simply logs the exception and rollback the transaction. - - :param model_name: model name on which the job method is located. - :param method_name: name of the method to call when this job is processed. - :param args: arguments of the method (without the usual self, cr, uid). - :param job_id: job id. - :param job_exception: exception raised by the job. - - """ + Simply logs the exception and rollback the transaction. """ self._cr.rollback() - _logger.exception("Call of self.env[%r].%s(*%r) failed in Job %s", - model_name, method_name, args, job_id) + _logger.exception("Call from cron %s for server action %d failed in Job %s", + cron_name, server_action_id, job_id) @api.model - def _callback(self, model_name, method_name, args, job_id): - """ Run the method associated to a given job - - It takes care of logging and exception handling. - - :param model_name: model name on which the job method is located. - :param method_name: name of the method to call when this job is processed. - :param args: arguments of the method (without the usual self, cr, uid). - :param job_id: job id. - """ + def _callback(self, cron_name, server_action_id, job_id): + """ Run the method associated to a given job. It takes care of logging + and exception handling. Note that the user running the server action + is the user calling this method. """ try: - args = str2tuple(args) if self.pool != self.pool.check_signaling(): # the registry has changed, reload self in the new registry self.env.reset() self = self.env()[self._name] - if model_name in self.env: - model = self.env[model_name] - if hasattr(model, method_name): - log_depth = (None if _logger.isEnabledFor(logging.DEBUG) else 1) - odoo.netsvc.log(_logger, logging.DEBUG, 'cron.object.execute', (self._cr.dbname, self._uid, '*', model_name, method_name)+tuple(args), depth=log_depth) - if _logger.isEnabledFor(logging.DEBUG): - start_time = time.time() - getattr(model, method_name)(*args) - if _logger.isEnabledFor(logging.DEBUG): - end_time = time.time() - _logger.debug('%.3fs (%s, %s)', end_time - start_time, model_name, method_name) - self.pool.signal_caches_change() - else: - _logger.warning("Method '%s.%s' does not exist.", model_name, method_name) - else: - _logger.warning("Model %r does not exist.", model_name) + + log_depth = (None if _logger.isEnabledFor(logging.DEBUG) else 1) + odoo.netsvc.log(_logger, logging.DEBUG, 'cron.object.execute', (self._cr.dbname, self._uid, '*', cron_name, server_action_id), depth=log_depth) + if _logger.isEnabledFor(logging.DEBUG): + start_time = time.time() + self.env['ir.actions.server'].browse(server_action_id).run() + if _logger.isEnabledFor(logging.DEBUG): + end_time = time.time() + _logger.debug('%.3fs (cron %s, server action %d with uid %d)', end_time - start_time, cron_name, server_action_id, self.env.uid) + self.pool.signal_caches_change() except Exception, e: - self._handle_callback_exception(model_name, method_name, args, job_id, e) + self._handle_callback_exception(cron_name, server_action_id, job_id, e) @classmethod def _process_job(cls, job_cr, job, cron_cr): @@ -151,7 +123,7 @@ class ir_cron(models.Model): if numbercall > 0: numbercall -= 1 if not ok or job['doall']: - cron._callback(job['model'], job['function'], job['args'], job['id']) + cron._callback(job['cron_name'], job['ir_actions_server_id'], job['id']) if numbercall: nextcall += _intervalTypes[job['interval_type']](job['interval_number']) ok = True @@ -223,10 +195,10 @@ class ir_cron(models.Model): locked_job = lock_cr.fetchone() if not locked_job: - _logger.debug("Job `%s` already executed by another process/thread. skipping it", job['name']) + _logger.debug("Job `%s` already executed by another process/thread. skipping it", job['cron_name']) continue # Got the lock on the job row, run its code - _logger.debug('Starting job `%s`.', job['name']) + _logger.debug('Starting job `%s`.', job['cron_name']) job_cr = db.cursor() try: registry = odoo.registry(db_name) @@ -239,7 +211,7 @@ class ir_cron(models.Model): except psycopg2.OperationalError, e: if e.pgcode == '55P03': # Class 55: Object not in prerequisite state; 55P03: lock_not_available - _logger.debug('Another process/thread is already busy executing job `%s`, skipping it.', job['name']) + _logger.debug('Another process/thread is already busy executing job `%s`, skipping it.', job['cron_name']) continue else: # Unexpected OperationalError diff --git a/odoo/addons/base/ir/ir_cron_view.xml b/odoo/addons/base/ir/ir_cron_view.xml index 57232252696..045fa688ef3 100644 --- a/odoo/addons/base/ir/ir_cron_view.xml +++ b/odoo/addons/base/ir/ir_cron_view.xml @@ -1,42 +1,28 @@ - + + ir.cron.view.form ir.cron + primary + -
    -
    -
    - - - - - - + +