3 |
Celery : Distributed Task Queue
4 |
Odoo integration of the Python #1 asynchronous task queue
5 |
6 |
7 |
Features:
8 |
9 |
10 |
11 | Put model-methods on the Celery Task Queue.
12 |
13 |
14 |
15 | Monitor and manage the Task Queue in Odoo.
16 |
17 |
18 |
19 | All Exceptions are catched and available as State=Failure with Exception message/trace shown.
20 |
21 |
22 |
23 | Requeue of Failed and Pending (stale) tasks.
24 |
25 |
26 |
27 | No complex installation and setup requirements are needed, except the Celery setup.
28 |
29 |
30 |
31 |
32 |
33 |
Check out the links below, for more info about Celery .
34 |
35 | Celery website
36 | http://celeryproject.org
37 |
38 |
39 | Celery installation
40 | http://docs.celeryproject.org/en/latest/getting-started/first-steps-with-celery.html
41 |
42 |
43 |
44 |
45 |
46 |
47 |
Put a model-method on the Celery Task Queue
48 | Just Python code to call from an Odoo model
49 |
50 |
51 |
Example
52 |
Model and method:
53 |
54 | model : "celery.example"
55 | method : "schedule_import"
56 |
57 |
58 |
Celery Options:
59 |
Shall be provided to kwargs
of the call_task
method
60 |
61 | celery = {
62 | 'queue': 'high.priority',
63 | 'countdown': 5,
64 | 'retry': True,
65 | 'retry_countdown_setting': 'MUL_RETRIES_SECS',
66 | 'retry_countdown_multiply_retries_seconds': 60,
67 | 'retry_policy': {
68 | 'interval_start': 30
69 | }
70 | }
71 |
Calling the task:
72 |
self.env["celery.task"].call_task("celery.example", "schedule_import", celery=celery)
73 |
74 |
75 |
76 |
Celery Options
77 |
All Celery options are optional (not required).
78 |
79 |
80 |
81 | Option
82 | Description
83 | Celery Documentation
84 |
85 |
86 |
87 |
88 | queue
89 | Name of the Celery/worker queue, the task shall be routed to.
90 | Routing Tasks: http://docs.celeryproject.org/en/latest/userguide/routing.html
91 |
92 |
93 | countdown
94 | The countdown is a shortcut to set ETA by seconds into the future.
95 | ETA and Countdown: http://docs.celeryproject.org/en/latest/userguide/calling.html#eta-and-countdown
96 |
97 |
98 | retry
99 | Set to True
to enable the retry of sending task messages.
100 | Message Sending Retry: http://docs.celeryproject.org/en/latest/userguide/calling.html#message-sending-retry
101 |
102 |
103 | retry_countdown_setting
104 | Specify whether and how to increase the Retry Countdown by:
105 |
106 | ADD_SECS:
107 | countdown = countdown + retry_countdown_add_seconds
108 | MUL_RETRIES:
109 | countdown = countdown * request.retries
110 | MUL_RETRIES_SECS:
111 | countdown = request.retries * retry_countdown_multiply_retries_seconds
112 |
113 |
114 | This is a custom option which affects the Celery countdown
option.
115 |
116 |
117 | retry_countdown_add_seconds
118 | Specify the seconds (integer field) to add to the Retry Countdown .
119 | This is a custom option which affects the Celery countdown
option.
120 |
121 |
122 |
123 | retry_countdown_multiply_retries_seconds
124 | Specify the seconds (integer field) to multiply with request retries, which becomes the Retry Countdown .
125 | This is a custom option which affects the Celery countdown
option.
126 |
127 |
128 | retry_policy
129 | Options when retrying publishing a task message in the case of connection loss or other connection errors.
130 | Message Sending Retry: http://docs.celeryproject.org/en/latest/userguide/calling.html#message-sending-retry
131 |
132 |
133 |
134 |
135 |
136 |
137 |
Extra kwargs
138 |
Extra kwargs
are optional (not required).
139 |
140 |
141 |
142 | Kwarg
143 | Description
144 |
145 |
146 | transaction_strategy
147 | Specifies when the task shall apply (ORM create and send to Celery MQ):
148 |
149 | after_commit
:
150 | Apply task after commit of the main/caller transaction (default setting).
151 |
152 | immediate
:
153 | Apply task immediately from the main/caller transaction, even if it ain't committed yet.
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
Monitor and control the Celery Task Queue
167 |
168 |
List of queued tasks
169 |
170 |
171 |
172 |
Task Failure info
173 |
174 |
175 |
176 |
Tasks waiting for Retry
177 |
178 |
179 |
180 |
Task waiting for Retry with Failure info
181 |
182 |
183 |
184 |
Requeue a Failed Task
185 |
186 |
187 |
188 |
Requeue multiple Failed Tasks
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
Installation and configuration
197 |
198 |
Celery and Message broker
199 |
200 |
201 | Celery Installation
202 | http://docs.celeryproject.org/en/latest/getting-started/first-steps-with-celery.html
203 |
204 |
205 |
206 |
207 |
Odoo configuration
208 |
All you need is to determine the Odoo Celery user and setup the credentials for XML-RPC authentication.
209 |
This enables the Celery process to authenticate in Odoo, by the XML-RPC server.
210 |
To support different kind of deployment and build tools, the credentials can either be setup as:
211 |
212 |
213 | (OS) Environment variables of the user running the Odoo server process: ODOO_CELERY_USER
and ODOO_CELERY_PASSWORD
214 |
215 |
216 | (OS) Environment variables for third-party broker: ODOO_CELERY_BROKER
, ODOO_CELERY_BROKER_HEARTBEAT
, ODOO_CELERY_WORKER_PREFETCH_MULTIPLIER
217 |
218 |
219 | Put in the odoo.conf
file, e.g:
220 |
221 | celery_user = Odoo-User
222 | celery_password = Odoo-User-Password
223 | See example.odoo.conf
, visit link: https://github.com/novacode-nl/odoo-celery/blob/14.0/celery/example.odoo.conf
224 |
225 |
226 | Put in the odoo.conf
file, under section [celery]
e.g:
227 |
228 | [celery]
229 | user = Odoo-User
230 | password = Odoo-User-Password
231 | See example_section_celery.odoo.conf
, visit link: https://github.com/novacode-nl/odoo-celery/blob/14.0/celery/example_section_celery.odoo.conf
232 |
233 |
234 |
235 |
Start the Celery (Odoo) worker server
236 |
Check the Celery Documentation for more ways to start and manage the server/proces. E.g. by Supervisor
237 |
The Celery (Odoo) worker => Python file odoo.py
, which is located directly under the "celery" module directory.
238 |
239 |
Start the worker (default/celery queue) on command line, whereas "odoo" references to the Python file:
240 |
# celery -A odoo worker --loglevel=info
241 |
242 |
Start a worker as a named queue:
243 |
# celery -A odoo worker --loglevel=info -Q high.priority -n high.priority
244 |
245 |
246 |
247 |
248 |
249 |
250 |
"Celery Example" module
251 |
Demo of 2 implemented example tasks, queued by 2 buttons
252 |
253 |
Check out the "Celery Example" module
254 |
255 | After installation, go to the menu "Celery / Example Task".
256 | Click button(s) "Queue create Line" shown in screensshot, which puts a task on the queue.
257 | Check the queue (menu: Celery / Tasks). Check the form of the Example record - new Lines had been created.
258 |
259 |
260 | Celery Examples module
261 | https://apps.odoo.com/apps/modules/11.0/celery_example/
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
Changelog
272 |
0.27
273 |
274 |
275 | Improvement: Add tracking for field exc_info
(Exception Info).
276 | (GitHub PR: https://github.com/novacode-nl/odoo-celery/pull/38
)
277 |
278 |
279 | Imrpvovement: Implement broker connection based on env.vars
280 | (GitHub PR: https://github.com/novacode-nl/odoo-celery/pull/41
)
281 |
282 |
283 |
0.26
284 |
285 |
286 | Fix: AccessDenied error is raised if no password was passed to the function, but this exception was not imported.
287 | (GitHub PR: https://github.com/novacode-nl/odoo-celery/pull/37
)
288 |
289 |
290 |
0.25
291 |
292 |
293 | Fix relating to 0.21 (support inactive Celery User). Inactive user not allowed anymore, since recent Odoo 14.0
294 | Override the res.users
access check
method: only for Celery user (from config) remove the active check.
295 |
296 |
0.24
297 |
298 |
299 | Fix crash (upon install, update) due to faulty config of Window Action "Flag/update task: Stuck" (action_celery_task_set_state_stuck
) - introduced by porting to Odoo 14.0.
300 |
301 |
302 |
0.23
303 |
304 |
305 | Fix cancellation of stucked tasks(s) wasn't possible.
306 |
307 |
308 | Fix requeue of stucked tasks(s) wasn't possible.
309 |
310 |
311 |
0.22
312 |
313 |
314 | Upon processing task rpc_run_task
(method), only update the related record fields (res_model
, res_ids
) if these have a value set in the method its result.
315 | This enables the API to store these related record fields upfront, when the task is created.
316 |
317 |
318 |
0.21
319 |
320 | Some security hardening: support inactive Celery User, which refuses regular (web) authentication/login.
321 |
322 |
0.20
323 |
324 |
325 | [FIX] In call_task()
the search of celery.task.setting
results in Access Denied by ACLs for operation: read, model: celery.task.setting
.
326 | Solved by calling the celery.task.setting
search()
with sudo()
.
327 |
328 |
0.19
329 |
330 | [FIX] Related record: res_ids (now ListSerialized field).
331 | Also change kwargs field to sensible class(name): TaskSerialized to KwargsSerialized.
332 |
333 |
0.18
334 |
335 | Configurable (database) transaction strategy, when the task shall apply (ORM create and send to Celery MQ).
336 | From now on - by default a task shall apply after commit of the main/caller transaction, instead of immediately in the main/caller transaction.
337 |
338 |
0.17
339 |
340 | Task scheduling added - being able to schedule tasks in a specified time interval or certain day(s) of the week.
341 | A new task state - Scheduled, is handled by an Odoo cron job - "Celery: Handle Scheduled Tasks".
342 |
343 |
0.16
344 |
345 | Configurable celery queues added to task settings.
346 |
347 |
0.15
348 |
349 | Scheduled (cron) cleanup of tasks - with optionally specifying/overriding: (1) timedelta (days=90, hours, minutes, seconds), (2) states to keep and (3) batch_size=100.
350 | Create database index for the State Time (state_date) field.
351 |
352 |
0.14
353 |
354 | Create database index for the Reference field (ref).
355 |
356 |
0.13
357 |
358 | Get XML-RPC URL from ir.config.parameter (celery.celery_base_url) by Settings.
359 |
360 |
0.12
361 |
362 | Also support to get the Celery (Odoo) user and password from the odoo.conf file, under section [options] too.
363 |
364 |
0.11
365 |
366 | Put task into a new state "Retrying", right before the Retry starts.
367 |
368 |
369 |
0.10
370 |
371 | Renamed task retry settings: 'MUL_RETR' to 'MUL_RETRIES', 'MUL_RETR_SECS' to 'MUL_RETRIES_SECS'.
372 |
373 |
374 |
0.9
375 |
376 |
377 | Hide (Odoo) password in the "retry" payload used by the MQ broker for XML-RPC authentication.
378 | GitHub issue: https://github.com/novacode-nl/odoo-celery/issues/17
379 |
380 |
381 |
0.8
382 |
383 | Fix task retry countdown/interval ignored. For more info see https://github.com/novacode-nl/odoo-celery/issues/14
384 | Add task retry countdown settings, to increase countdown during retries ('ADD_SECS', 'MUL_RETRY', 'MUL_RETRY_SECS').
385 | Search view of stuck tasks: (1) Add/show field reference
(2) search-view with filters and grouping.
386 | Track task changes of fields model, method, handle_stuck
387 | Disable create/copy in the task form-view.
388 |
389 |
0.7
390 |
391 | Task Reference , which serves:
392 |
393 | Easier searching for tasks by Reference.
394 | Check (ORM query) before call_task()
call, to prevent redundant creation of recurring Pending tasks.
395 |
396 |
397 |
398 |
0.6
399 |
Introduction of "Stuck" tasks. A stuck task could be caused, for example by: a stopped/restarted Celery worker, message broker, or server.
400 |
401 | Settings: to specify when a task (model, method) is Stuck after seconds from Started or Retry (states).
402 | Stuck Tasks Report: which shows Stuck (not completed) tasks.
403 | Cron or manually put tasks in "Stuck" state.
404 | Ability to cancel (Pending or Stuck) tasks, which never completed.
405 | Track and messaging about state change. Chatter on form view.
406 |
407 |
0.5
408 |
409 | Routing tasks to specific (named) queues.
410 |
411 |
0.4
412 |
413 | FIX: Store task state (Started, Retry) before execution.
414 |
415 |
0.3
416 |
417 |
418 | Hide (Odoo) password in payload used by the broker for XML-RPC authentication.
419 | GitHub issue: https://github.com/novacode-nl/odoo-celery/issues/4
420 |
421 |
422 |
0.2
423 |
424 | Task state information.
425 |
426 |
0.1
427 |
428 | Initial version.
429 |
430 |
431 |
432 |
--------------------------------------------------------------------------------
/celery/static/description/odoo-example-task-form.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/novacode-nl/odoo-celery/ec02f2e37c724665b26b5023ef8f4ac0463ae1b9/celery/static/description/odoo-example-task-form.png
--------------------------------------------------------------------------------
/celery/static/description/odoo-example-task-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/novacode-nl/odoo-celery/ec02f2e37c724665b26b5023ef8f4ac0463ae1b9/celery/static/description/odoo-example-task-list.png
--------------------------------------------------------------------------------
/celery/static/description/odoo-requeue-multiple-tasks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/novacode-nl/odoo-celery/ec02f2e37c724665b26b5023ef8f4ac0463ae1b9/celery/static/description/odoo-requeue-multiple-tasks.png
--------------------------------------------------------------------------------
/celery/static/description/odoo-requeue-single-task.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/novacode-nl/odoo-celery/ec02f2e37c724665b26b5023ef8f4ac0463ae1b9/celery/static/description/odoo-requeue-single-task.png
--------------------------------------------------------------------------------
/celery/static/description/odoo-task-failure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/novacode-nl/odoo-celery/ec02f2e37c724665b26b5023ef8f4ac0463ae1b9/celery/static/description/odoo-task-failure.png
--------------------------------------------------------------------------------
/celery/static/description/odoo-task-queue-failure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/novacode-nl/odoo-celery/ec02f2e37c724665b26b5023ef8f4ac0463ae1b9/celery/static/description/odoo-task-queue-failure.png
--------------------------------------------------------------------------------
/celery/static/description/odoo-task-queue-retry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/novacode-nl/odoo-celery/ec02f2e37c724665b26b5023ef8f4ac0463ae1b9/celery/static/description/odoo-task-queue-retry.png
--------------------------------------------------------------------------------
/celery/static/description/odoo-task-retry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/novacode-nl/odoo-celery/ec02f2e37c724665b26b5023ef8f4ac0463ae1b9/celery/static/description/odoo-task-retry.png
--------------------------------------------------------------------------------
/celery/tests/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright Nova Code (http://www.novacode.nl)
2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
3 |
4 | from . import test_celery_task
5 |
--------------------------------------------------------------------------------
/celery/tests/test_celery_task.py:
--------------------------------------------------------------------------------
1 | # Copyright Nova Code (http://www.novacode.nl)
2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
3 |
4 | import json
5 | import uuid
6 | import pytz
7 |
8 | from odoo import fields
9 | from odoo.exceptions import UserError, ValidationError
10 | from odoo.tools.misc import mute_logger
11 | from odoo.tests.common import TransactionCase
12 |
13 | from ..models.celery_task import CeleryTask, STATE_PENDING, STATE_STARTED, STATE_RETRY
14 |
15 |
16 | class TestCeleryTask(TransactionCase):
17 |
18 | def setUp(self):
19 | super(TestCeleryTask, self).setUp()
20 |
21 | def test_unlink_started_task(self):
22 | """ Unlink STARTED task """
23 |
24 | Task = self.env['celery.task']
25 | vals = {
26 | 'uuid': str(uuid.uuid4()),
27 | 'user_id': self.env.user.id,
28 | 'model': 'celery.task',
29 | 'method': 'dummy_method',
30 | }
31 | task = Task.create(vals)
32 |
33 | task.state = STATE_STARTED
34 | with self.assertRaisesRegex(UserError, 'You cannot delete a running task'), mute_logger('odoo.sql_db'):
35 | task.unlink()
36 |
37 | def test_unlink_retry_task(self):
38 | """ Unlink RETRY task """
39 |
40 | Task = self.env['celery.task']
41 | vals = {
42 | 'uuid': str(uuid.uuid4()),
43 | 'user_id': self.env.user.id,
44 | 'model': 'celery.task',
45 | 'method': 'dummy_method',
46 | }
47 | task = Task.create(vals)
48 |
49 | task.state = STATE_STARTED
50 | with self.assertRaisesRegex(UserError, 'You cannot delete a running task'), mute_logger('odoo.sql_db'):
51 | task.unlink()
52 |
53 | def test_write_task_update_celery_kwargs(self):
54 | """ Write task (Celery param fields) update Celery kwargs """
55 |
56 | Task = self.env['celery.task']
57 |
58 | vals = {
59 | 'uuid': str(uuid.uuid4()),
60 | 'user_id': self.env.user.id,
61 | 'model': 'celery.task',
62 | 'method': 'dummy_method',
63 | 'kwargs': {'celery': {'retry': False,'countdown': 3}}
64 | }
65 | task = Task.create(vals)
66 | task.write({
67 | 'retry': True,
68 | 'max_retries': 5,
69 | 'countdown': 10})
70 |
71 | kwargs = json.loads(task.kwargs)
72 |
73 | self.assertTrue(kwargs['celery']['retry'])
74 | self.assertEqual(kwargs['celery']['max_retries'], 5)
75 | self.assertEqual(kwargs['celery']['countdown'], 10)
76 |
77 | def test_scheduled_task(self):
78 | """ Creates a task setting scheduling the tasks to a time-window between 23:30 and 23:36 UTC """
79 |
80 | self.env['res.users'].sudo().browse(self.env.uid).tz = 'UTC'
81 | vals = {
82 | 'model': 'celery.task',
83 | 'method': 'dummy_method_schedule',
84 | 'schedule': True,
85 | 'schedule_hours_from': 23.5,
86 | 'schedule_hours_to': 23.6,
87 | }
88 | task_setting = self.env['celery.task.setting'].create(vals)
89 |
90 | now = fields.datetime.now().replace(tzinfo=pytz.UTC)
91 | scheduled_date = self.env['celery.task'].check_schedule_needed(task_setting)
92 |
93 | if now.hour == 23 and now.minute > 30 and now.minute < 36:
94 | self.assertEqual(scheduled_date, False)
95 | else:
96 | self.assertEqual(scheduled_date.hour, 23)
97 | self.assertEqual(scheduled_date.minute, 30)
98 |
--------------------------------------------------------------------------------
/celery/views/celery_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
17 |
22 |
23 |
24 |
28 |
33 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/celery/views/celery_queue_views.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 | celery.queue.tree
9 | celery.queue
10 | tree
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | celery.queue.form
22 | celery.queue
23 | form
24 |
25 |
51 |
52 |
53 |
54 |
55 |
56 | celery.queue.kanban
57 | celery.queue
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | Total tasks: ( percent of all tasks)
90 |
91 |
92 | Pending tasks:
93 |
94 |
95 | Added in the last 24h:
96 |
97 |
98 | Succeded in the last 24h:
99 |
100 |
101 | Failed in the last 24h:
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | celery.queue
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | Celery Queues
129 | celery.queue
130 | kanban,tree,form
131 | {'compute_queue_stats': True}
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/celery/views/celery_task_setting_views.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 | celery.task.setting.tree
9 | celery.task.setting
10 | tree
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | celery.task.setting.form
27 | celery.task.setting
28 | form
29 |
30 |
110 |
111 |
112 |
113 |
114 | celery.task.setting
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | Celery Task Settings
131 | celery.task.setting
132 | tree,form
133 | {'search_default_all': 1}
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/celery/views/celery_task_views.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 | celery.task.tree
9 | celery.task
10 | tree
11 |
12 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | celery.task.form
31 | celery.task
32 | form
33 |
34 |
120 |
121 |
122 |
123 |
124 | celery.task.search
125 | celery.task
126 | search
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
138 |
140 |
142 |
144 |
146 |
148 |
150 |
152 |
153 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | Celery Tasks
177 | celery.task
178 | tree,form
179 |
180 |
181 |
182 |
183 |
--------------------------------------------------------------------------------
/celery/views/res_config_settings_views.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 | res.config.settings.celery.view.form
8 | res.config.settings
9 |
10 |
11 |
12 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/celery/wizard/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright Nova Code (http://www.novacode.nl)
2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
3 |
4 | from . import celery_requeue_task
5 | from . import celery_cancel_task
6 | from . import celery_handle_stuck_task
7 |
--------------------------------------------------------------------------------
/celery/wizard/celery_cancel_task.py:
--------------------------------------------------------------------------------
1 | # Copyright Nova Code (http://www.novacode.nl)
2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
3 |
4 | from odoo import api, fields, models
5 |
6 | from ..models.celery_task import STATES_TO_CANCEL
7 |
8 |
9 | class CancelTask(models.TransientModel):
10 | _name = 'celery.cancel.task'
11 | _description = 'Celery Cancel Task Wizard'
12 |
13 | @api.model
14 | def _default_task_ids(self):
15 | states_to_cancel = self.env['celery.task']._states_to_cancel()
16 | res = False
17 | context = self.env.context
18 | if (context.get('active_model') == 'celery.task' and
19 | context.get('active_ids')):
20 | task_ids = context['active_ids']
21 | res = self.env['celery.task'].search([
22 | ('id', 'in', context['active_ids']),
23 | '|',
24 | ('state', 'in', states_to_cancel),
25 | ('stuck', '=', True)
26 | ]).ids
27 | return res
28 |
29 | task_ids = fields.Many2many('celery.task', string='Tasks', default=_default_task_ids)
30 |
31 | def action_cancel(self):
32 | self.task_ids.action_cancel()
33 | return {'type': 'ir.actions.act_window_close'}
34 |
--------------------------------------------------------------------------------
/celery/wizard/celery_cancel_task_views.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 | Cancel Task
9 | celery.cancel.task
10 |
11 |
20 |
21 |
22 |
23 |
24 | Cancel Task
25 | celery.cancel.task
26 | form
27 |
28 | new
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/celery/wizard/celery_handle_stuck_task.py:
--------------------------------------------------------------------------------
1 | # Copyright Nova Code (http://www.novacode.nl)
2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
3 |
4 | from odoo import api, fields, models
5 |
6 | from ..models.celery_task import STATES_TO_STUCK
7 |
8 |
9 | class CeleryHandleStuckJask(models.TransientModel):
10 | _name = 'celery.handle.stuck.task'
11 | _description = 'Handle Stuck Task'
12 |
13 | @api.model
14 | def _default_task_ids(self):
15 | res = False
16 | context = self.env.context
17 | if (context.get('active_model') == 'celery.stuck.task.report' and
18 | context.get('active_ids')):
19 | task_ids = context['active_ids']
20 | res = self.env['celery.task'].search([
21 | ('id', 'in', context['active_ids']),
22 | ('state', 'in', STATES_TO_STUCK)]).ids
23 | return res
24 |
25 | task_ids = fields.Many2many(
26 | 'celery.task', string='Tasks', default=_default_task_ids,
27 | domain=[('state', 'in', STATES_TO_STUCK)])
28 |
29 | def action_handle_stuck_tasks(self):
30 | self.task_ids.action_stuck()
31 | return {'type': 'ir.actions.act_window_close'}
32 |
--------------------------------------------------------------------------------
/celery/wizard/celery_handle_stuck_task_views.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 | Celery Handle Stuck Task
9 | celery.handle.stuck.task
10 |
11 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/celery/wizard/celery_requeue_task.py:
--------------------------------------------------------------------------------
1 | # Copyright Nova Code (http://www.novacode.nl)
2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
3 |
4 | from odoo import api, fields, models
5 |
6 | from ..models.celery_task import STATES_TO_REQUEUE
7 |
8 |
9 | class RequeueTask(models.TransientModel):
10 | _name = 'celery.requeue.task'
11 | _description = 'Celery Requeue Tasks Wizard'
12 |
13 | @api.model
14 | def _default_task_ids(self):
15 | res = False
16 | context = self.env.context
17 | if (context.get('active_model') == 'celery.task' and
18 | context.get('active_ids')):
19 | task_ids = context['active_ids']
20 | res = self.env['celery.task'].search([
21 | ('id', 'in', context['active_ids']),
22 | '|',
23 | ('state', 'in', STATES_TO_REQUEUE),
24 | ('stuck', '=', True)
25 | ]).ids
26 | return res
27 |
28 | task_ids = fields.Many2many(
29 | 'celery.task', string='Tasks', default=_default_task_ids,
30 | domain=['|', ('stuck', '=', True), ('state', 'in', STATES_TO_REQUEUE)])
31 |
32 | def action_requeue(self):
33 | self.task_ids.action_requeue()
34 | return {'type': 'ir.actions.act_window_close'}
35 |
--------------------------------------------------------------------------------
/celery/wizard/celery_requeue_task_views.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 | Requeue Tasks
9 | celery.requeue.task
10 |
11 |
20 |
21 |
22 |
23 |
24 | Requeue Task
25 | celery.requeue.task
26 | form
27 |
28 | new
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/celery_example/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Nova Code (http://www.novacode.nl)
2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
3 |
4 | from . import models
5 |
--------------------------------------------------------------------------------
/celery_example/__manifest__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Nova Code (http://www.novacode.nl)
2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
3 | {
4 | 'name': 'Celery Examples',
5 | 'summary': 'Celery Example tasks ready to run from Odoo.',
6 | 'category': 'Extra Tools',
7 | 'version': '0.2',
8 | 'description': """Put example tasks on the Celery Task Queue.""",
9 | 'author': 'Nova Code',
10 | 'website': 'https://www.novacode.nl',
11 | 'license': "LGPL-3",
12 | 'depends': ['celery'],
13 | 'data': [
14 | 'data/celery_example_data.xml',
15 | 'security/ir_model_access.xml',
16 | 'views/celery_example_views.xml'
17 | ],
18 | 'images': [
19 | 'static/description/banner.png',
20 | ],
21 | 'installable': True,
22 | 'application' : False,
23 | }
24 |
--------------------------------------------------------------------------------
/celery_example/data/celery_example_data.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 | Example Task
9 |
10 |
11 |
12 | Celery Example: Schedule example
13 |
14 | code
15 | model._cron_schedule_example()
16 |
17 |
18 | 1
19 | hours
20 | -1
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/celery_example/models/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Nova Code (http://www.novacode.nl)
2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
3 |
4 | from . import celery_example
5 |
--------------------------------------------------------------------------------
/celery_example/models/celery_example.py:
--------------------------------------------------------------------------------
1 | # Copyright Nova Code (http://www.novacode.nl)
2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
3 |
4 | import logging
5 | import time
6 |
7 | from odoo import api, fields, models, _
8 | from odoo.addons.celery.models.celery_task import RETRY_COUNTDOWN_MULTIPLY_RETRIES
9 |
10 | _logger = logging.getLogger(__name__)
11 |
12 |
13 | class CeleryExample(models.Model):
14 | _name = 'celery.example'
15 | _description = 'Celery Example'
16 |
17 | name = fields.Char(default='Celery Example', required=True)
18 | lines = fields.One2many('celery.example.line', 'example_id', string='Lines')
19 |
20 | def action_task_with_reference(self):
21 | celery = {
22 | 'countdown': 10, 'retry': True,
23 | 'retry_policy': {'max_retries': 2, 'interval_start': 2}
24 | }
25 | celery_task_vals = {
26 | 'ref': 'celery.example.task_with_reference'
27 | }
28 | self.env["celery.task"].call_task("celery.example", "task_with_reference", example_id=self.id, celery_task_vals=celery_task_vals, celery=celery)
29 |
30 | def action_task_immediate(self):
31 | celery = {
32 | 'countdown': 10, 'retry': True,
33 | 'retry_policy': {'max_retries': 2, 'interval_start': 2}
34 | }
35 | celery_task_vals = {
36 | 'ref': 'celery.example.task_immediate'
37 | }
38 | self.env["celery.task"].call_task(
39 | "celery.example", "task_immediate",
40 | example_id=self.id,
41 | celery_task_vals=celery_task_vals,
42 | celery=celery,
43 | transaction_strategy='immediate')
44 |
45 | def action_task_with_error(self):
46 | celery = {
47 | 'countdown': 2,
48 | 'retry': True,
49 | 'max_retries': 4,
50 | 'retry_countdown_setting': 'MUL_RETRIES_SECS',
51 | 'retry_countdown_multiply_retries_seconds': 5,
52 | 'retry_policy': {'interval_start': 2}
53 | }
54 | celery_task_vals = {
55 | 'ref': 'celery.example.task_with_error'
56 | }
57 | self.env["celery.task"].call_task("celery.example", "task_with_error", example_id=self.id, celery=celery)
58 |
59 | def action_task_queue_default(self):
60 | celery = {
61 | 'countdown': 3, 'retry': True,
62 | 'retry_policy': {'max_retries': 2, 'interval_start': 2}
63 | }
64 | self.env["celery.task"].call_task("celery.example", "task_queue_default", example_id=self.id, celery=celery)
65 |
66 | def action_task_queue_high(self):
67 | celery = {
68 | 'queue': 'high.priority', 'countdown': 2, 'retry': True,
69 | 'retry_policy': {'max_retries': 2, 'interval_start': 2}
70 | }
71 | self.env["celery.task"].call_task("celery.example", "task_queue_high", example_id=self.id, celery=celery)
72 |
73 | def action_task_queue_low(self):
74 | celery = {
75 | 'queue': 'low.priority', 'countdown': 2, 'retry': True,
76 | 'retry_policy': {'max_retries': 2, 'interval_start': 2}
77 | }
78 | self.env["celery.task"].call_task("celery.example", "task_queue_low", example_id=self.id, celery=celery)
79 |
80 | @api.model
81 | def task_with_reference(self, task_uuid, **kwargs):
82 | task = 'task_with_reference'
83 | example_id = kwargs.get('example_id')
84 | res = self.env['celery.example.line'].create({
85 | 'name': task,
86 | 'example_id': example_id
87 | })
88 | msg = 'CELERY called task: model [%s] and method [%s].' % (self._name, task)
89 | _logger.info(msg)
90 | return {'result': msg, 'res_model': 'celery.example.line', 'res_ids': [res.id]}
91 |
92 | @api.model
93 | def task_immediate(self, task_uuid, **kwargs):
94 | task = 'task_immediate'
95 | example_id = kwargs.get('example_id')
96 | self.env['celery.example.line'].create({
97 | 'name': task,
98 | 'example_id': example_id
99 | })
100 | msg = 'CELERY called task: model [%s] and method [%s].' % (self._name, task)
101 | _logger.info(msg)
102 | return msg
103 |
104 | @api.model
105 | def task_with_error(self, task_uuid, **kwargs):
106 | task = 'task_with_error'
107 | _logger.critical('RETRY of %s' % task)
108 |
109 | example_id = kwargs.get('example_id')
110 | self.env['celery.example.line'].create({
111 | 'example_id': example_id
112 | })
113 | msg = 'CELERY called task: model [%s] and method [%s].' % (self._name, task)
114 | _logger.info(msg)
115 | return msg
116 |
117 | @api.model
118 | def task_queue_default(self, task_uuid, **kwargs):
119 | task = 'task_queue_default'
120 | example_id = kwargs.get('example_id')
121 | self.env['celery.example.line'].create({
122 | 'name': task,
123 | 'example_id': example_id
124 | })
125 | msg = 'CELERY called task: model [%s] and method [%s].' % (self._name, task)
126 | _logger.info(msg)
127 | return msg
128 |
129 | @api.model
130 | def task_queue_high(self, task_uuid, **kwargs):
131 | time.sleep(2)
132 | task = 'task_queue_high'
133 | example_id = kwargs.get('example_id')
134 | self.env['celery.example.line'].create({
135 | 'name': task,
136 | 'example_id': example_id
137 | })
138 | msg = 'CELERY called task: model [%s] and method [%s].' % (self._name, task)
139 | _logger.info(msg)
140 | return msg
141 |
142 | @api.model
143 | def task_queue_low(self, task_uuid, **kwargs):
144 | time.sleep(5)
145 |
146 | task = 'task_queue_low'
147 | example_id = kwargs.get('example_id')
148 | self.env['celery.example.line'].create({
149 | 'name': task,
150 | 'example_id': example_id
151 | })
152 | msg = 'CELERY called task: model [%s] and method [%s].' % (self._name, task)
153 | _logger.info(msg)
154 | return msg
155 |
156 | def _cron_schedule_example(self):
157 | self.env['celery.task'].call_task(self._name, 'schedule_cron_example')
158 |
159 | @api.model
160 | def schedule_cron_example(self, task_uuid, **kwargs):
161 | self.env['celery.task'].call_task(
162 | self._name, 'run_cron_example')
163 |
164 | msg = 'Schedule Cron Example'
165 | _logger.critical(msg)
166 | return {'result': msg}
167 |
168 | @api.model
169 | def run_cron_example(self, task_uuid, **kwargs):
170 | msg = 'Run Cron Example'
171 | _logger.critical(msg)
172 | return {'result': msg}
173 |
174 | def refresh_view(self):
175 | return True
176 |
177 |
178 | class CeleryExampleLine(models.Model):
179 | _name = 'celery.example.line'
180 | _description = 'Celery Example Line'
181 |
182 | name = fields.Char(required=True)
183 | example_id = fields.Many2one('celery.example', string='Example', required=True, ondelete='cascade')
184 |
--------------------------------------------------------------------------------
/celery_example/security/ir_model_access.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 | celery.example: Celery Manager
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | celery.example: Celery RPC
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | celery.example.line: Celery Manager
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | celery.example.line: Celery RPC
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/celery_example/static/description/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/novacode-nl/odoo-celery/ec02f2e37c724665b26b5023ef8f4ac0463ae1b9/celery_example/static/description/banner.png
--------------------------------------------------------------------------------
/celery_example/static/description/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/novacode-nl/odoo-celery/ec02f2e37c724665b26b5023ef8f4ac0463ae1b9/celery_example/static/description/icon.png
--------------------------------------------------------------------------------
/celery_example/views/celery_example_views.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 | celery.example.tree
9 | celery.example
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | celery.example.form
26 | celery.example
27 | form
28 |
29 |
54 |
55 |
56 |
57 |
58 | Celery Example Tasks
59 | celery.example
60 | tree,form
61 |
62 |
63 |
64 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | celery
2 |
--------------------------------------------------------------------------------