Simone Orsi
6 years ago
12 changed files with 242 additions and 40 deletions
-
21base_cron_oneshot/README.rst
-
13base_cron_oneshot/__manifest__.py
-
18base_cron_oneshot/data/ir_cron.xml
-
2base_cron_oneshot/data/ir_sequence.xml
-
97base_cron_oneshot/models/ir_cron.py
-
2base_cron_oneshot/readme/CONTRIBUTORS.rst
-
8base_cron_oneshot/readme/DESCRIPTION.rst
-
4base_cron_oneshot/readme/HISTORY.rst
-
20base_cron_oneshot/readme/USAGE.rst
-
1base_cron_oneshot/tests/__init__.py
-
94base_cron_oneshot/tests/test_cron.py
-
2base_cron_oneshot/views/ir_cron.xml
@ -0,0 +1,21 @@ |
|||||
|
**This file is going to be generated by oca-gen-addon-readme.** |
||||
|
|
||||
|
*Manual changes will be overwritten.* |
||||
|
|
||||
|
Please provide content in the ``readme`` directory: |
||||
|
|
||||
|
* **DESCRIPTION.rst** (required) |
||||
|
* INSTALL.rst (optional) |
||||
|
* CONFIGURE.rst (optional) |
||||
|
* **USAGE.rst** (optional, highly recommended) |
||||
|
* DEVELOP.rst (optional) |
||||
|
* ROADMAP.rst (optional) |
||||
|
* HISTORY.rst (optional, recommended) |
||||
|
* **CONTRIBUTORS.rst** (optional, highly recommended) |
||||
|
* CREDITS.rst (optional) |
||||
|
|
||||
|
Content of this README will also be drawn from the addon manifest, |
||||
|
from keys such as name, authors, maintainers, development_status, |
||||
|
and license. |
||||
|
|
||||
|
A good, one sentence summary in the manifest is also highly recommended. |
@ -1,18 +1,17 @@ |
|||||
# Copyright (C) 2018 by Camptocamp |
|
||||
|
# Copyright (C) 2018 Camptocamp |
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
{ |
{ |
||||
'name': """Single use crons""", |
|
||||
'summary': """Allows creating of single-use disposable crons""", |
|
||||
|
'name': """Oneshot cron""", |
||||
|
'summary': """Allows creating of single-use disposable crons.""", |
||||
'category': "Extra Tools", |
'category': "Extra Tools", |
||||
'version': "11.0.1.0.0", |
'version': "11.0.1.0.0", |
||||
|
|
||||
'author': "Camptocamp SA, " |
|
||||
|
'author': "Camptocamp, " |
||||
"Odoo Community Association (OCA)", |
"Odoo Community Association (OCA)", |
||||
'website': "https://github.com/OCA/server-tools", |
'website': "https://github.com/OCA/server-tools", |
||||
'license': "AGPL-3", |
'license': "AGPL-3", |
||||
|
|
||||
'data': [ |
'data': [ |
||||
'views/ir_cron.xml', |
|
||||
'data/ir_sequence.xml', |
'data/ir_sequence.xml', |
||||
|
'data/ir_cron.xml', |
||||
|
'views/ir_cron.xml', |
||||
], |
], |
||||
} |
} |
@ -0,0 +1,18 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
<data noupdate="1"> |
||||
|
<record id="cron_oneshot_cleanup" model="ir.cron" forcecreate="True"> |
||||
|
<field name="name">Oneshot cron cleanup</field> |
||||
|
<field name="user_id" ref="base.user_root"/> |
||||
|
<field name="model_id" ref="model_ir_cron"/> |
||||
|
<field name="state">code</field> |
||||
|
<field name="code">model.cron_oneshot_cleanup()</field> |
||||
|
<field name="interval_number">1</field> |
||||
|
<field name="interval_type">days</field> |
||||
|
<!-- make it run the day after installation at midnight --> |
||||
|
<field name="nextcall" eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d 00:00:00')" /> |
||||
|
<field name="numbercall">-1</field> |
||||
|
<field name="doall" eval="False"/> |
||||
|
</record> |
||||
|
</data> |
||||
|
</odoo> |
@ -0,0 +1,2 @@ |
|||||
|
* Simone Orsi <simone.orsi@camptocamp.com> |
||||
|
* Artem Kostyuk <a.kostyuk@mobilunity.com> |
@ -0,0 +1,8 @@ |
|||||
|
This module extends the functionality of Odoo crons |
||||
|
to allow you to create single-purpose crons without any further setup or modules |
||||
|
such as `queue_job`. |
||||
|
|
||||
|
The typical use case is: you have an expensive task to run on demand and only once. |
||||
|
|
||||
|
A main cron called "Oneshot cron cleanup" will delete already executed crons each day. |
||||
|
You might want to tune it according to your needs. |
@ -0,0 +1,4 @@ |
|||||
|
11.0.1.0.0 (2018-08-30) |
||||
|
~~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
* First release |
@ -0,0 +1,20 @@ |
|||||
|
You can create crons as usual via the admin interface or via code. |
||||
|
The important thing, in both case, is to set `oneshot` flag as true. |
||||
|
|
||||
|
Developer shortcut |
||||
|
------------------ |
||||
|
|
||||
|
You can easily create a oneshot cron like this: |
||||
|
|
||||
|
.. code-block:: python |
||||
|
|
||||
|
cron = self.env['ir.cron'].schedule_oneshot( |
||||
|
'res.partner', method='my_cron_method') |
||||
|
|
||||
|
If you need to customize other parameters you can pass them as keyword args: |
||||
|
|
||||
|
.. code-block:: python |
||||
|
|
||||
|
my_values = {...} |
||||
|
cron = self.env['ir.cron'].schedule_oneshot( |
||||
|
'res.partner', method='my_cron_method', **my_values) |
@ -0,0 +1 @@ |
|||||
|
from . import test_cron |
@ -0,0 +1,94 @@ |
|||||
|
# Copyright (C) 2018 Camptocamp |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
# pylint disable=anomalous-backslash-in-string |
||||
|
|
||||
|
from odoo import api |
||||
|
from odoo.tests import common |
||||
|
|
||||
|
import datetime |
||||
|
import mock |
||||
|
|
||||
|
|
||||
|
MOCK_PATH = 'odoo.addons.base_cron_oneshot.models.ir_cron' |
||||
|
|
||||
|
|
||||
|
class OneshotTestCase(common.SavepointCase): |
||||
|
|
||||
|
@property |
||||
|
def cron_model(self): |
||||
|
return self.env['ir.cron'] |
||||
|
|
||||
|
@mock.patch(MOCK_PATH + '.datetime') |
||||
|
def test_defaults(self, mocked_dt): |
||||
|
mocked_dt.now.return_value = datetime.datetime(2018, 8, 31, 10, 30) |
||||
|
cron = self.cron_model.create({ |
||||
|
'oneshot': True, |
||||
|
'name': 'Foo', |
||||
|
'model_id': self.env['ir.model']._get('ir.cron').id, |
||||
|
'state': 'code', |
||||
|
'code': 'model.some_method()', |
||||
|
'interval_number': 1, |
||||
|
'interval_type': 'days', |
||||
|
'numbercall': 5, # won't have any effect |
||||
|
}) |
||||
|
self.assertRegexpMatches(cron.name, 'Oneshot#\d+ Foo') |
||||
|
self.assertEqual(cron.numbercall, 1) |
||||
|
# call postponed by 10mins |
||||
|
self.assertEqual(cron.nextcall, '2018-08-31 10:40:00') |
||||
|
|
||||
|
def test_schedule_oneshot_check(self): |
||||
|
with self.assertRaises(AssertionError) as err: |
||||
|
self.cron_model.schedule_oneshot('res.partner') |
||||
|
self.assertEqual(str(err.exception), 'Provide a method or some code!') |
||||
|
|
||||
|
@mock.patch(MOCK_PATH + '.datetime') |
||||
|
def test_schedule_oneshot_method(self, mocked_dt): |
||||
|
mocked_dt.now.return_value = datetime.datetime(2018, 8, 31, 16, 30) |
||||
|
cron = self.cron_model.schedule_oneshot( |
||||
|
'res.partner', method='read', delay=('minutes', 30)) |
||||
|
self.assertRegexpMatches(cron.name, 'Oneshot#\d+') |
||||
|
self.assertEqual(cron.numbercall, 1) |
||||
|
self.assertEqual(cron.code, 'model.read()') |
||||
|
self.assertEqual( |
||||
|
cron.model_id, self.env['ir.model']._get('res.partner')) |
||||
|
self.assertEqual(cron.nextcall, '2018-08-31 17:00:00') |
||||
|
|
||||
|
def test_schedule_oneshot_code(self): |
||||
|
cron = self.cron_model.schedule_oneshot( |
||||
|
'res.partner', code='env["res.partner"].search([])') |
||||
|
self.assertRegexpMatches(cron.name, 'Oneshot#\d+') |
||||
|
self.assertEqual(cron.numbercall, 1) |
||||
|
self.assertEqual(cron.state, 'code') |
||||
|
self.assertEqual(cron.code, 'env["res.partner"].search([])') |
||||
|
self.assertEqual( |
||||
|
cron.model_id, self.env['ir.model']._get('res.partner')) |
||||
|
|
||||
|
|
||||
|
class OneshotProcessTestCase(common.TransactionCase): |
||||
|
|
||||
|
def setUp(self): |
||||
|
super().setUp() |
||||
|
deleted = [] |
||||
|
|
||||
|
@api.multi |
||||
|
def unlink(self): |
||||
|
deleted.extend(self.ids) |
||||
|
# do nothing as the original one will try to read the lock |
||||
|
# for the current record which is NOT committed |
||||
|
# and has no real ID. |
||||
|
return |
||||
|
|
||||
|
self.env['ir.cron']._patch_method('unlink', unlink) |
||||
|
self.addCleanup(self.env['ir.cron']._revert_method, 'unlink') |
||||
|
self.deleted = deleted |
||||
|
|
||||
|
def test_schedule_oneshot_cleanup(self): |
||||
|
cron1 = self.env['ir.cron'].schedule_oneshot( |
||||
|
'res.partner', code='env["res.partner"].search([])') |
||||
|
cron2 = self.env['ir.cron'].schedule_oneshot( |
||||
|
'res.partner', code='env["res.partner"].read([])') |
||||
|
# simulate excuted |
||||
|
cron1.write({'numbercall': 0, 'active': False}) |
||||
|
self.env['ir.cron'].cron_oneshot_cleanup() |
||||
|
self.assertIn(cron1.id, self.deleted) |
||||
|
self.assertNotIn(cron2.id, self.deleted) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue