wcs/wcs/admin/logged_errors.py

231 lines
8.8 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, Null, Or
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(),
},
)
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_html_traceback(self):
r = TemplateIO(html=True)
parts = (
N_('Exception'),
N_('Stack trace (most recent call first)'),
N_('Form'),
N_('Cookies'),
N_('Environment'),
)
current_part = None
for line in self.error.traceback.splitlines():
if line.endswith(':') and line.rstrip(':') in parts:
if current_part in parts[:2]:
r += htmltext('</pre></div>')
elif current_part:
r += htmltext('</table></div>')
current_part = line.rstrip(':')
r += htmltext('<div class="section"><h3>%s</h3>') % _(current_part)
if current_part in parts[:2]:
r += htmltext('<pre class="traceback">')
else:
r += htmltext('<table class="main compact code">')
continue
if current_part in parts[: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[:2]:
r += htmltext('</pre></div>')
elif current_part:
r += htmltext('</table></div>')
return r.getvalue()
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, with_total=False
):
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(Or([NotEqual('formdef_class', 'FormDef'), Null('formdef_class')]))
if not backoffice_root.is_accessible('cards'):
clauses.append(Or([NotEqual('formdef_class', 'CardDef'), Null('formdef_class')]))
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 = 0
if with_total:
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 = cls.get_errors(
offset=0, limit=4, formdef_class=formdef_class, formdef_id=formdef_id, workflow_id=workflow_id
)[0]
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', len(errors))
% {'count': len(errors)}
)
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,
with_total=True,
)
links = ''
if get_publisher().is_using_postgresql():
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)