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). |
|||
{ |
|||
'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", |
|||
'version': "11.0.1.0.0", |
|||
|
|||
'author': "Camptocamp SA, " |
|||
'author': "Camptocamp, " |
|||
"Odoo Community Association (OCA)", |
|||
'website': "https://github.com/OCA/server-tools", |
|||
'license': "AGPL-3", |
|||
|
|||
'data': [ |
|||
'views/ir_cron.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