misc: display a technical error on form with removed fields (#61255)

This commit is contained in:
Frédéric Péters 2022-01-31 16:06:47 +01:00
parent 04136fe6dc
commit 909f16eff3
8 changed files with 65 additions and 17 deletions

View File

@ -1639,3 +1639,25 @@ def test_workflow_display_form_with_block_add(pub):
'blah_var_data_raw': {'data': [{'123': 'foo'}, {'123': 'bar'}], 'schema': {'123': 'string'}},
'blah_var_str': 'blah',
}
def test_removed_block_in_form_page(pub):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'form title'
formdef.fields = [
fields.BlockField(id='0', label='test', type='block:removed'),
]
formdef.store()
resp = get_app(pub).get(formdef.get_url(), status=500)
assert 'A fatal error happened.' in resp.text
if pub.is_using_postgresql():
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert '(id:%s)' % logged_error.id in resp.text
resp = get_app(pub).get(formdef.get_url(), status=500)
assert '(id:%s)' % logged_error.id in resp.text
logged_error = pub.loggederror_class.select()[0]
assert logged_error.occurences_count == 2

View File

@ -91,6 +91,14 @@ class SetValueError(Exception):
pass
class MissingBlockFieldError(Exception):
def __init__(self, block_slug):
self.block_slug = block_slug
def __str__(self):
return force_text(_('Missing block field: %s') % self.block_slug)
class PrefillSelectionWidget(CompositeWidget):
def __init__(self, name, value=None, field=None, **kwargs):
CompositeWidget.__init__(self, name, value, **kwargs)
@ -3478,6 +3486,13 @@ class BlockField(WidgetField):
def get_dependencies(self):
yield self.block
def add_to_form(self, form, value=None):
try:
self.block
except KeyError:
raise MissingBlockFieldError(self.type[6:])
return super().add_to_form(form, value=value)
def fill_admin_form(self, form):
super().fill_admin_form(form)
if form.get_widget('prefill'):

View File

@ -34,7 +34,7 @@ from quixote.html import TemplateIO, htmltext
from quixote.util import randbytes
from wcs.categories import Category
from wcs.fields import SetValueError
from wcs.fields import MissingBlockFieldError, SetValueError
from wcs.formdata import Evolution, FormData
from wcs.formdef import FormDef
from wcs.forms.common import FormStatusPage, FormTemplateMixin
@ -528,8 +528,14 @@ class FormPage(Directory, FormTemplateMixin):
form_data.update(computed_data)
self.feed_current_data(magictoken)
with get_publisher().substitutions.temporary_feed(transient_formdata, force_mode='lazy'):
form = self.create_form(page, displayed_fields, transient_formdata=transient_formdata)
try:
with get_publisher().substitutions.temporary_feed(transient_formdata, force_mode='lazy'):
form = self.create_form(page, displayed_fields, transient_formdata=transient_formdata)
except MissingBlockFieldError as e:
logged_error = get_publisher().record_error(
str(e), exception=e, notify=True, formdef=self.formdef
)
raise errors.InternalServerError(logged_error)
if submit_button is True:
# submit_button at True means a non-submitting button has been

View File

@ -94,8 +94,7 @@ class LoggedError:
error.tech_id = error.build_tech_id()
error.occurences_count += 1
error.latest_occurence_timestamp = now()
error.store()
return error
return error.store()
def record_new_occurence(self, error):
if not self.id:
@ -112,7 +111,7 @@ class LoggedError:
# exception should be the same (same tech_id), record just in case
self.exception_class = error.exception_class
self.exception_message = error.exception_message
self.store()
return self.store()
@classmethod
def record_error(cls, error_summary, plain_error_msg, publisher, kind=None, *args, **kwargs):

View File

@ -430,7 +430,7 @@ class WcsPublisher(QommonPublisher):
)
if not notify or logged_exception and logged_exception.occurences_count > 1:
# notify only first occurence
return
return logged_exception
try:
self.logger.log_internal_error(
error_summary, plain_error_msg, logged_exception.tech_id if logged_exception else None
@ -440,6 +440,7 @@ class WcsPublisher(QommonPublisher):
# were configured to be mailed. (formerly socket.error)
# Could also could happen on file descriptor exhaustion.
pass
return logged_exception
def apply_global_action_timeouts(self, **kwargs):
from wcs.workflows import Workflow, WorkflowGlobalActionTimeoutTrigger

View File

@ -18,7 +18,7 @@ import urllib.parse
import quixote
from quixote import get_publisher
from quixote.errors import AccessError, TraversalError
from quixote.errors import AccessError, PublishError, TraversalError
from quixote.html import TemplateIO, htmltext
from . import _, template
@ -77,20 +77,23 @@ class EmailError(Exception):
pass
class InternalServerError:
class InternalServerError(PublishError):
status_code = 500
def __init__(self, logged_error):
super().__init__(_('Technical error'))
self.logged_error = logged_error
def render(self):
from . import _
template.html_top(_('Oops, the server borked severely'))
template.html_top(_('Technical error'))
r = TemplateIO(html=True)
r += htmltext('<p>')
r += _('This is bad bad bad; perhaps you will have more luck if ' 'you retry in a few minutes ? ')
r += _(
'Alternatively you could harass the webmaster (who may have '
'been emailed automatically with this incident but you can\'t '
'be sure about this.'
)
r += _('A fatal error happened. It has been recorded and will be available to administrators.')
if self.logged_error:
r += ' (id:%s)' % self.logged_error.id
r += htmltext('</p>')
return r.getvalue()

View File

@ -185,7 +185,7 @@ class QommonPublisher(Publisher):
def format_publish_error(self, exc):
get_response().filter = {}
if isinstance(exc, errors.AccessError) and hasattr(exc, 'render'):
if isinstance(exc, errors.PublishError) and hasattr(exc, 'render'):
return exc.render()
return errors.format_publish_error(exc)

View File

@ -3582,6 +3582,7 @@ class LoggedError(SqlMixin, wcs.logged_errors.LoggedError):
sql_dict = {x: getattr(self, x) for x, y in self._table_static_fields}
conn, cur = get_connection_and_cursor()
error = self
if not self.id:
existing_errors = list(self.get_with_indexed_value('tech_id', self.tech_id))
if not existing_errors:
@ -3613,6 +3614,7 @@ class LoggedError(SqlMixin, wcs.logged_errors.LoggedError):
assert cur.fetchone() is not None, 'LoggedError id not found'
conn.commit()
cur.close()
return error
@classmethod
def _row2ob(cls, row, **kwargs):