231 lines
8.8 KiB
Python
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)
|