diff --git a/trytond/ir/cron.py b/trytond/ir/cron.py
index baaa584ef6..ed61297af3 100644
--- a/trytond/ir/cron.py
+++ b/trytond/ir/cron.py
@@ -11,6 +11,8 @@ from mx import DateTime
from trytond import pooler
from trytond.osv import fields, OSV
import datetime
+import traceback
+import sys
_INTERVALTYPES = {
'work_days': lambda interval: DateTime.RelativeDateTime(days=interval),
@@ -26,7 +28,11 @@ class Cron(OSV):
_name = "ir.cron"
_description = __doc__
name = fields.Char('Name', required=True)
- user = fields.Many2One('res.user', 'User', required=True)
+ user = fields.Many2One('res.user', 'Execution User', required=True,
+ help="The user used to execute this action")
+ request_user = fields.Many2One(
+ 'res.user', 'Request User', required=True,
+ help="The user who will receive requests in case of failure")
active = fields.Boolean('Active', select=1)
interval_number = fields.Integer('Interval Number')
interval_type = fields.Selection( [
@@ -51,6 +57,15 @@ class Cron(OSV):
help='0=Very Urgent\n10=Not urgent')
running = fields.Boolean('Running', readonly=True, select=1)
+ def __init__(self):
+ super(Cron, self).__init__()
+ self._error_messages.update({
+ 'request_title': 'Scheduled action failed',
+ 'request_body': "The following action failed to execute "
+ "properly: \"%s\" \n See the first attachment for "
+ "details.\n Traceback: \n\n%s\n"
+ })
+
def default_nextcall(self, cursor, user, context=None):
return datetime.datetime.now()
@@ -86,39 +101,33 @@ class Cron(OSV):
fct(cursor, user, *args)
def pool_jobs(self, db_name):
- #TODO Error treatment: exception, request, ... -> send request to user
now = DateTime.now()
- try:
- cursor = pooler.get_db(db_name).cursor()
- except:
- return False
+ cursor = pooler.get_db(db_name).cursor()
+ cursor.execute('SELECT * FROM ir_cron ' \
+ 'WHERE numbercall <> 0 ' \
+ 'AND active ' \
+ 'AND nextcall <= now() ' \
+ 'AND NOT running ' \
+ 'ORDER BY priority')
+ crons = cursor.dictfetchall()
- try:
- cursor.execute('SELECT * FROM ir_cron ' \
- 'WHERE numbercall <> 0 ' \
- 'AND active ' \
- 'AND nextcall <= now() ' \
- 'AND NOT running ' \
- 'ORDER BY priority')
- jobs = cursor.dictfetchall()
- if jobs:
+ for cron in crons:
+ try:
cursor.execute('UPDATE ir_cron SET running = True ' \
- 'WHERE id in (' + ','.join(['%s' for x in jobs]) + ')',
- [x['id'] for x in jobs])
- for job in jobs:
- nextcall = DateTime.strptime(str(job['nextcall']),
+ 'WHERE id = %s' % cron['id'])
+ nextcall = DateTime.strptime(str(cron['nextcall']),
'%Y-%m-%d %H:%M:%S')
- numbercall = job['numbercall']
+ numbercall = cron['numbercall']
done = False
while nextcall < now and numbercall:
if numbercall > 0:
numbercall -= 1
- if not done or job['doall']:
- self._callback(cursor, job['user'], job['id'], job['model'],
- job['function'], job['args'])
+ if not done or cron['doall']:
+ self._callback(cursor, cron['user'], cron['id'], cron['model'],
+ cron['function'], cron['args'])
if numbercall:
- nextcall += _INTERVALTYPES[job['interval_type']](
- job['interval_number'])
+ nextcall += _INTERVALTYPES[cron['interval_type']](
+ cron['interval_number'])
done = True
addsql = ''
if not numbercall:
@@ -127,9 +136,40 @@ class Cron(OSV):
"running = False, numbercall = %s" + addsql + " " \
"WHERE id = %s",
(nextcall.strftime('%Y-%m-%d %H:%M:%S'),
- numbercall, job['id']))
+ numbercall, cron['id']))
cursor.commit()
- finally:
- cursor.close()
+ except Exception, error:
+ cursor.rollback()
+
+ tb_s = ''
+ for line in traceback.format_exception(*sys.exc_info()):
+ try:
+ line = line.encode('utf-8', 'ignore')
+ except:
+ continue
+ tb_s += line
+
+ request_obj = self.pool.get('res.request')
+ try:
+ request_obj.create(
+ cursor, cron['user'],
+ {'name': self.raise_user_error(
+ cursor, 'request_title', raise_exception=False),
+ 'priority': '2',
+ 'act_from': cron['user'],
+ 'act_to': cron['request_user'],
+ 'body': self.raise_user_error(
+ cursor, 'request_body', (cron['name'], tb_s),
+ raise_exception=False),
+ 'date_sent': now,
+ 'references': [
+ ('create',{'reference': "ir.cron,%s"%cron['id']})],
+ 'state': 'waiting',
+ 'trigger_date': now,
+ })
+ cursor.commit()
+ except:
+ cursor.rollback()
+ cursor.close()
Cron()
diff --git a/trytond/ir/cron.xml b/trytond/ir/cron.xml
index f1dfed7f00..37dd355f4f 100644
--- a/trytond/ir/cron.xml
+++ b/trytond/ir/cron.xml
@@ -14,6 +14,7 @@
+
@@ -34,17 +35,18 @@
-
-
+
+
+
+
+
+
-
-
-
diff --git a/trytond/osv/orm.py b/trytond/osv/orm.py
index 6f58f0e9a8..a250e85cd9 100644
--- a/trytond/osv/orm.py
+++ b/trytond/osv/orm.py
@@ -668,7 +668,8 @@ class ORM(object):
return None
def raise_user_error(self, cursor, error, error_args=None,
- error_description='', error_description_args=None, context=None):
+ error_description='', error_description_args=None,
+ raise_exception=True, context=None):
'''
Raise an exception that will be display as an error message
in the client.
@@ -682,6 +683,9 @@ class ORM(object):
_error_messages used for error description
:param error_description_args: the arguments that will be used
for "%"-based substitution
+ :param raise_exception: if set to False return the error string
+ (or tuple if error_description is not empty) instead of raising an
+ exception.
:param context: the context in which the language key will
be used for translation
'''
@@ -725,9 +729,14 @@ class ORM(object):
if error_description_args:
error_description = error_description % error_description_args
-
- raise Exception('UserError', error, error_description)
- raise Exception('UserError', error)
+ if raise_exception:
+ raise Exception('UserError', error, error_description)
+ else:
+ return (error, error_description)
+ if raise_exception:
+ raise Exception('UserError', error)
+ else:
+ return error
def browse(self, cursor, user, ids, context=None):
'''
diff --git a/trytond/res/ir.xml b/trytond/res/ir.xml
index 7fcf684b3a..f086449b81 100644
--- a/trytond/res/ir.xml
+++ b/trytond/res/ir.xml
@@ -458,5 +458,9 @@
]]>
+
+ Scheduler
+ ir.cron
+