wcs/wcs/admin/logged_errors.py

240 lines
9.2 KiB
Python

# w.c.s. - web application for online forms
# Copyright (C) 2005-2017 Entr'ouvert
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
import re
from django.utils.text import Truncator
from quixote import get_publisher, get_request, get_response, redirect
from quixote.directory import Directory
from quixote.html import TemplateIO, htmltext
from wcs.qommon import N_, _, errors, misc, ngettext, template
from wcs.qommon.backoffice.listing import pagination_links
from wcs.qommon.storage import Equal, NotEqual, NotNull
class LoggedErrorDirectory(Directory):
_q_exports = ['', 'delete', 'ack']
do_not_call_in_templates = True
def __init__(self, parent_dir, error):
self.parent_dir = parent_dir
self.error = error
def _q_index(self):
get_response().breadcrumb.append(('%s/' % self.error.id, self.error.summary))
self.parent_dir.html_top(_('Logged Errors - %s') % self.error.summary)
get_response().filter['sidebar'] = self.get_sidebar()
return template.QommonTemplateResponse(
templates=['wcs/backoffice/logged-error.html'],
context={
'view': self,
'error': self.error,
'formdef': self.error.get_formdef(),
'workflow': self.error.get_workflow(),
'status': self.error.get_status(),
'status_item': self.error.get_status_item(),
'formdata': self.error.get_formdata(),
'tabs': self.get_tabs(),
},
)
def error_expression_type_label(self):
return {
'python': _('Python Expression'),
'django': _('Django Expression'),
'template': _('Template'),
'text': _('Text'),
}.get(self.error.expression_type, _('Unknown'))
def get_tabs(self):
r = TemplateIO(html=True)
parts = (
('exception', N_('Exception')),
('stack-trace', N_('Stack trace (most recent call first)')),
('form', N_('Form')),
('request-cookies', N_('Cookies')),
('environment', N_('Environment')),
)
parts_labels = [x[1] for x in parts]
current_part = None
tabs = []
for line in self.error.traceback.splitlines():
if line.endswith(':') and line.rstrip(':') in parts_labels:
if current_part in parts_labels[:2]:
r += htmltext('</pre>')
elif current_part:
r += htmltext('</table>')
current_part = line.rstrip(':')
part_slug = [x[0] for x in parts if x[1] == current_part][0]
if not (part_slug == 'stack-trace' and tabs):
r = TemplateIO(html=True)
tabs.append(
{
'slug': part_slug,
'label': _(current_part),
'content': r,
}
)
if part_slug == 'stack-trace':
tabs[-1]['label'] = _('Stack trace')
if current_part in parts_labels[:2]:
r += htmltext('<pre class="traceback %s">' % part_slug)
else:
r += htmltext('<table class="main compact code">')
continue
if current_part in parts_labels[:2]:
r += line + '\n'
elif line:
r += htmltext('<tr><td>%s</td><td>%s</td></tr>') % tuple(re.split(r'\s+', line, maxsplit=1))
if current_part in parts_labels[:2]:
r += htmltext('</pre>')
elif current_part:
r += htmltext('</table>')
return tabs
def get_sidebar(self):
r = TemplateIO(html=True)
r += htmltext('<ul id="sidebar-actions">')
r += htmltext('<li><a href="delete">%s</a></li>') % _('Delete')
r += htmltext('</ul>')
return r.getvalue()
def delete(self):
get_publisher().loggederror_class.remove_object(self.error.id)
return redirect('..')
class LoggedErrorsDirectory(Directory):
_q_exports = ['']
@classmethod
def get_errors(cls, offset, limit, formdef_class=None, formdef_id=None, workflow_id=None):
errors = []
if not get_publisher().loggederror_class:
return errors, 0
select_kwargs = {
'order_by': '-first_occurence_timestamp',
'limit': limit,
'offset': offset,
}
clauses = []
if formdef_id and formdef_class:
clauses = [Equal('formdef_id', formdef_id), Equal('formdef_class', formdef_class.__name__)]
elif workflow_id:
clauses = [Equal('workflow_id', workflow_id)]
else:
# check permissions, exclude errors related to not accessible items
clauses = []
backoffice_root = get_publisher().get_backoffice_root()
if not backoffice_root.is_accessible('forms'):
clauses.append(NotEqual('formdef_class', 'FormDef'))
if not backoffice_root.is_accessible('cards'):
clauses.append(NotEqual('formdef_class', 'CardDef'))
if not backoffice_root.is_accessible('workflows'):
# exclude workflow-only errors
clauses.append(NotNull('formdef_class'))
errors = get_publisher().loggederror_class.select(clause=clauses, **select_kwargs)
count = get_publisher().loggederror_class.count(clauses)
return list(errors), count
@classmethod
def errors_block(cls, formdef_class=None, formdef_id=None, workflow_id=None):
# select 3 + 1 last errors
errors, total = cls.get_errors(
offset=0, limit=4, formdef_class=formdef_class, formdef_id=formdef_id, workflow_id=workflow_id
)
if not errors:
return ''
r = TemplateIO(html=True)
r += htmltext('<div class="bo-block logged-errors">')
r += (
htmltext('<h3><a href="logged-errors/">%s</a></h3>')
% ngettext('%(count)d error', '%(count)d errors', total)
% {'count': total}
)
r += htmltext('<ul>')
for error in errors[:3]:
r += htmltext('<li><a href="logged-errors/%s/">%s</a> ') % (error.id, error.summary)
if error.exception_class or error.exception_message:
message = _('error %(class)s (%(message)s)') % {
'class': error.exception_class,
'message': error.exception_message,
}
message = Truncator(message).chars(80, truncate='')
r += htmltext(message)
r += htmltext('</li>')
if len(errors) > 3:
r += htmltext('<li>...</li>')
r += htmltext('</ul>')
r += htmltext('</div>')
return r.getvalue()
def __init__(self, parent_dir, formdef_class=None, formdef_id=None, workflow_id=None):
self.parent_dir = parent_dir
self.formdef_class = formdef_class
self.formdef_id = formdef_id
self.workflow_id = workflow_id
def _q_index(self):
backoffice_root = get_publisher().get_backoffice_root()
if not (
backoffice_root.is_accessible('forms')
or backoffice_root.is_accessible('cards')
or backoffice_root.is_accessible('workflows')
):
raise errors.AccessForbiddenError()
get_response().breadcrumb.append(('logged-errors/', _('Logged Errors')))
self.parent_dir.html_top(_('Logged Errors'))
limit = misc.get_int_or_400(
get_request().form.get('limit', get_publisher().get_site_option('default-page-size')) or 20
)
offset = misc.get_int_or_400(get_request().form.get('offset', 0))
logged_errors, total_count = self.get_errors(
offset=offset,
limit=limit,
formdef_class=self.formdef_class,
formdef_id=self.formdef_id,
workflow_id=self.workflow_id,
)
links = ''
links = pagination_links(offset, limit, total_count, load_js=False)
return template.QommonTemplateResponse(
templates=['wcs/backoffice/logged-errors.html'],
context={
'errors': logged_errors,
'pagination_links': links,
},
)
def _q_lookup(self, component):
try:
error = get_publisher().loggederror_class.get(component)
except KeyError:
raise errors.TraversalError()
get_response().breadcrumb.append(('logged-errors/', _('Logged Errors')))
return LoggedErrorDirectory(self.parent_dir, error)