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] [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 + -
-
-
- - - - - - + +