Initial import from Docker volume
This commit is contained in:
188
ir/error.py
Executable file
188
ir/error.py
Executable file
@@ -0,0 +1,188 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
import datetime as dt
|
||||
import functools
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
from trytond.config import config
|
||||
from trytond.exceptions import UserError, UserWarning
|
||||
from trytond.model import (
|
||||
Index, ModelSQL, ModelView, Workflow, dualmethod, fields)
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Eval
|
||||
from trytond.tools import firstline
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
clean_days = config.getint('error', 'clean_days', default=90)
|
||||
|
||||
|
||||
def set_user(field):
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(cls, records, *args, **kwargs):
|
||||
result = func(cls, records, *args, **kwargs)
|
||||
cls.write(
|
||||
[r for r in records
|
||||
if not getattr(r, field)], {
|
||||
field: Transaction().user,
|
||||
})
|
||||
return result
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
def reset_user(*fields):
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(cls, records, *args, **kwargs):
|
||||
result = func(cls, records, *args, **kwargs)
|
||||
cls.write(records, {f: None for f in fields})
|
||||
return result
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
class Error(Workflow, ModelView, ModelSQL):
|
||||
"Error"
|
||||
__name__ = 'ir.error'
|
||||
|
||||
origin = fields.Reference("Origin", [
|
||||
('ir.cron', "Action"),
|
||||
('ir.queue', "Task"),
|
||||
], readonly=True)
|
||||
origin_string = origin.translated('origin')
|
||||
message = fields.Text("Message", readonly=True)
|
||||
description = fields.Text("Description", readonly=True)
|
||||
summary = fields.Function(fields.Char("Summary"), 'on_change_with_summary')
|
||||
|
||||
processed_by = fields.Many2One(
|
||||
'res.user', "Processed by",
|
||||
states={
|
||||
'readonly': Eval('state').in_(['processing', 'solved']),
|
||||
},
|
||||
depends=['state'])
|
||||
solved_by = fields.Many2One(
|
||||
'res.user', "Solved by",
|
||||
states={
|
||||
'readonly': Eval('state').in_(['solved']),
|
||||
},
|
||||
depends=['state'])
|
||||
|
||||
state = fields.Selection([
|
||||
('open', "Open"),
|
||||
('processing', "Processing"),
|
||||
('solved', "Solved"),
|
||||
], "State", readonly=True, sort=False)
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
table = cls.__table__()
|
||||
|
||||
cls._sql_indexes.add(
|
||||
Index(
|
||||
table,
|
||||
(table.state, Index.Equality()),
|
||||
where=table.state.in_(['open', 'processing'])))
|
||||
cls._transitions |= {
|
||||
('open', 'processing'),
|
||||
('processing', 'solved'),
|
||||
('processing', 'open'),
|
||||
}
|
||||
cls._buttons.update({
|
||||
'open': {
|
||||
'invisible': Eval('state') != 'processing',
|
||||
'depends': ['state'],
|
||||
},
|
||||
'process': {
|
||||
'invisible': Eval('state') != 'open',
|
||||
'depends': ['state'],
|
||||
},
|
||||
'solve': {
|
||||
'invisible': Eval('state') != 'processing',
|
||||
'depends': ['state'],
|
||||
},
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def default_state(cls):
|
||||
return 'open'
|
||||
|
||||
@fields.depends('message')
|
||||
def on_change_with_summary(self, name=None):
|
||||
return firstline(self.message or '')
|
||||
|
||||
def get_rec_name(self, name):
|
||||
if self.origin:
|
||||
return "%s - %s" % (self.origin_string, self.origin.rec_name)
|
||||
return super().get_rec_name(name)
|
||||
|
||||
@dualmethod
|
||||
def log(cls, *args, **kwargs):
|
||||
# Test if it is a ModelStorage.log call
|
||||
if len(args) <= 1 or not isinstance(args[1], Exception):
|
||||
return super().log(*args, **kwargs)
|
||||
warnings.warn(
|
||||
"Call report instead of log to store exception",
|
||||
DeprecationWarning)
|
||||
cls.report(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def report(cls, origin, exception):
|
||||
try:
|
||||
assert isinstance(exception, (UserError, UserWarning))
|
||||
with Transaction().new_transaction(autocommit=True):
|
||||
if not cls.search([
|
||||
('origin', '=', str(origin)),
|
||||
('message', '=', exception.message),
|
||||
('description', '=', exception.description),
|
||||
('state', '!=', 'solved'),
|
||||
]):
|
||||
cls.create([{
|
||||
'origin': str(origin),
|
||||
'message': exception.message,
|
||||
'description': exception.description,
|
||||
}])
|
||||
except Exception:
|
||||
logger.critical(
|
||||
"failed to store exception %s of %s", exception, origin,
|
||||
exc_info=True)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, date=None):
|
||||
if date is None:
|
||||
date = (
|
||||
dt.datetime.now() - dt.timedelta(days=clean_days))
|
||||
errors = cls.search([('create_date', '<', date)])
|
||||
cls.delete(errors)
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('open')
|
||||
@reset_user('processed_by')
|
||||
def open(cls, errors):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('processing')
|
||||
@set_user('processed_by')
|
||||
def process(cls, errors):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('solved')
|
||||
@set_user('solved_by')
|
||||
def solve(cls, errors):
|
||||
pool = Pool()
|
||||
Cron = pool.get('ir.cron')
|
||||
Queue = pool.get('ir.queue')
|
||||
for error in errors:
|
||||
if isinstance(error.origin, Cron):
|
||||
Cron.__queue__.run_once([error.origin])
|
||||
elif isinstance(error.origin, Queue):
|
||||
task = error.origin
|
||||
Queue.push(task.name, task.data)
|
||||
Reference in New Issue
Block a user