Files
tradon/ir/error.py
2025-12-26 13:11:43 +00:00

189 lines
5.9 KiB
Python
Executable File

# 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)