misc: store LoggedErrors in SQL (#48925)

This commit is contained in:
Lauréline Guérin 2020-11-30 18:29:09 +01:00
parent 62cf149fe7
commit 433757d383
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
24 changed files with 497 additions and 292 deletions

View File

@ -12,7 +12,6 @@ import pytest
from webtest import Upload
from wcs.qommon.http_request import HTTPRequest
from wcs.logged_errors import LoggedError
from wcs.roles import Role
from wcs.workflows import (
Workflow, WorkflowCriticalityLevel, DisplayMessageWorkflowStatusItem,
@ -1779,9 +1778,13 @@ def test_workflows_wscall_options(pub, value):
def test_workflows_wscall_status_error(pub):
if not pub.is_using_postgresql():
pytest.skip('this requires SQL')
return
create_superuser(pub)
LoggedError.wipe()
pub.loggederror_class.wipe()
Workflow.wipe()
workflow = Workflow(name='foo')
baz_status = workflow.add_status(name='baz')
@ -1798,14 +1801,14 @@ def test_workflows_wscall_status_error(pub):
app = login(get_app(pub))
app.get('/backoffice/workflows/%s/' % workflow.id)
assert LoggedError.count() == 0
assert pub.loggederror_class.count() == 0
# delete foo status
del workflow.possible_status[1]
workflow.store()
app.get('/backoffice/workflows/%s/' % workflow.id)
assert LoggedError.count() == 1
error = LoggedError.select()[0]
assert pub.loggederror_class.count() == 1
error = pub.loggederror_class.select()[0]
assert error.tech_id == '%s-reference-to-invalid-status-in-workflow-foo-status-baz-item-webservice' % workflow.id
assert error.formdef_id is None
assert error.workflow_id == workflow.id

View File

@ -39,7 +39,6 @@ from wcs.wf.create_carddata import CreateCarddataWorkflowStatusItem
from wcs.carddef import CardDef
from wcs.categories import Category
from wcs.formdef import FormDef
from wcs.logged_errors import LoggedError
from wcs import fields
from wcs.wscalls import NamedWsCall
@ -2257,11 +2256,16 @@ def test_backoffice_wscall_failure_display(http_requests, pub):
@pytest.mark.parametrize('notify_on_errors', [True, False])
@pytest.mark.parametrize('record_on_errors', [True, False])
def test_backoffice_wscall_on_error(http_requests, pub, emails, notify_on_errors, record_on_errors):
if not pub.is_using_postgresql():
pytest.skip('this requires SQL')
return
pub.cfg['debug'] = {'error_email': 'errors@localhost.invalid'}
pub.write_cfg()
create_user(pub)
create_environment(pub)
pub.loggederror_class.wipe()
formdef = FormDef.get_by_urlname('form-title')
form_class = formdef.data_class()
@ -2314,12 +2318,12 @@ def test_backoffice_wscall_on_error(http_requests, pub, emails, notify_on_errors
else:
assert emails.count() == 0
# check LoggedError
# check pub.loggederror_class
if record_on_errors:
assert LoggedError.count() == 1
LoggedError.wipe()
assert pub.loggederror_class.count() == 1
pub.loggederror_class.wipe()
else:
assert LoggedError.count() == 0
assert pub.loggederror_class.count() == 0
def test_backoffice_wscall_attachment(http_requests, pub):
@ -4709,6 +4713,10 @@ def test_backoffice_fields(pub):
def test_backoffice_logged_errors(pub):
if not pub.is_using_postgresql():
pytest.skip('this requires SQL')
return
Workflow.wipe()
workflow = Workflow.get_default_workflow()
workflow.id = '12'
@ -4738,7 +4746,7 @@ def test_backoffice_logged_errors(pub):
carddef.fields = []
carddef.store()
LoggedError.wipe()
pub.loggederror_class.wipe()
create_superuser(pub)
app = login(get_app(pub))
@ -4753,7 +4761,7 @@ def test_backoffice_logged_errors(pub):
resp = app.get('/test/')
resp = resp.form.submit('submit').follow()
resp = resp.form.submit('submit')
assert LoggedError.count() == 1
assert pub.loggederror_class.count() == 1
app = login(get_app(pub))
resp = app.get('/backoffice/forms/%s/' % formdef.id)
@ -4773,9 +4781,9 @@ def test_backoffice_logged_errors(pub):
assert not 'Acked' in resp.text
resp = resp.click('Ack').follow()
assert 'Acked' in resp.text
assert LoggedError.select()[0].acked is True
assert pub.loggederror_class.select()[0].acked is True
resp = resp.click('Delete').follow()
assert LoggedError.count() == 0
assert pub.loggederror_class.count() == 0
pub.cfg.update({'debug': {'error_email': None}})
pub.write_cfg()
@ -4784,7 +4792,7 @@ def test_backoffice_logged_errors(pub):
resp = app.get('/test/')
resp = resp.form.submit('submit').follow()
resp = resp.form.submit('submit')
assert LoggedError.count() == 1
assert pub.loggederror_class.count() == 1
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/%s/' % workflow.id)
@ -4802,7 +4810,7 @@ def test_backoffice_logged_errors(pub):
resp = app.get('/test/')
resp = resp.form.submit('submit').follow()
resp = resp.form.submit('submit')
assert LoggedError.count() == 2
assert pub.loggederror_class.count() == 2
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/%s/' % workflow.id)
@ -5338,11 +5346,12 @@ def create_formdata(request, pub):
source_formdef.store()
source_formdef.data_class().wipe()
target_formdef.data_class().wipe()
LoggedError.wipe()
if pub.is_using_postgresql():
pub.loggederror_class.wipe()
return locals()
def test_backoffice_create_formdata_backoffice_submission(create_formdata):
def test_backoffice_create_formdata_backoffice_submission(pub, create_formdata):
# create submitting user
user = create_formdata['pub'].user_class()
user.name = 'Jean Darmette'
@ -5373,7 +5382,8 @@ def test_backoffice_create_formdata_backoffice_submission(create_formdata):
assert target_data_class.count() == 0
# resubmit it through backoffice submission
resp = resp.form.submit(name='button_resubmit')
assert LoggedError.count() == 0
if pub.is_using_postgresql():
assert pub.loggederror_class.count() == 0
assert target_data_class.count() == 1
target_formdata = target_data_class.select()[0]
@ -5402,7 +5412,7 @@ def test_backoffice_create_formdata_backoffice_submission(create_formdata):
assert pq('.field-type-file .value').text() == 'bar'
def test_linked_forms_variables(create_formdata):
def test_linked_forms_variables(pub, create_formdata):
# create source formdata
formdata = create_formdata['source_formdef'].data_class()()
upload = PicklableUpload('/foo/bar', content_type='text/plain')
@ -5419,9 +5429,9 @@ def test_linked_forms_variables(create_formdata):
formdata.perform_workflow()
formdata.store()
get_publisher().substitutions.reset()
get_publisher().substitutions.feed(formdata)
substvars = get_publisher().substitutions.get_context_variables(mode='lazy')
pub.substitutions.reset()
pub.substitutions.feed(formdata)
substvars = pub.substitutions.get_context_variables(mode='lazy')
assert str(substvars['form_links_resubmitted_form_var_foo_string']) == 'coucou'
assert 'form_links_resubmitted_form_var_foo_string' in substvars.get_flat_keys()
@ -5440,7 +5450,7 @@ def test_linked_forms_variables(create_formdata):
assert 'form_links_resubmitted_form_var_foo_string' not in resp
def test_backoffice_create_formdata_map_fields_by_varname(create_formdata):
def test_backoffice_create_formdata_map_fields_by_varname(pub, create_formdata):
create_formdata['create_formdata'].map_fields_by_varname = True
create_formdata['create_formdata'].mappings = []
create_formdata['wf'].store()
@ -5490,7 +5500,8 @@ def test_backoffice_create_formdata_map_fields_by_varname(create_formdata):
assert target_data_class.count() == 0
# resubmit it through backoffice submission
resp = resp.form.submit(name='button_resubmit')
assert LoggedError.count() == 0
if pub.is_using_postgresql():
assert pub.loggederror_class.count() == 0
assert target_data_class.count() == 1
target_formdata = target_data_class.select()[0]

View File

@ -11,7 +11,6 @@ from wcs.blocks import BlockDef
from wcs.carddef import CardDef
from wcs.categories import CardDefCategory
from wcs.formdef import FormDef
from wcs.logged_errors import LoggedError
from wcs.qommon.http_request import HTTPRequest
from wcs.wf.wscall import WebserviceCallStatusItem
from wcs.workflows import ChoiceWorkflowStatusItem, Workflow
@ -480,7 +479,11 @@ def test_backoffice_cards_import_data_csv_invalid_columns(pub):
def test_backoffice_cards_wscall_failure_display(http_requests, pub, studio):
LoggedError.wipe()
if not pub.is_using_postgresql():
pytest.skip('this requires SQL')
return
pub.loggederror_class.wipe()
user = create_user(pub)
Workflow.wipe()
@ -536,8 +539,8 @@ def test_backoffice_cards_wscall_failure_display(http_requests, pub, studio):
resp = resp.follow()
assert 'Error during webservice call' in resp.text
assert LoggedError.count() == 1
assert LoggedError.select()[0].get_formdata().data == {'1': 'plop'}
assert pub.loggederror_class.count() == 1
assert pub.loggederror_class.select()[0].get_formdata().data == {'1': 'plop'}
def test_block_card_item_link(pub, studio, blocks_feature):

View File

@ -50,7 +50,6 @@ from wcs.tracking_code import TrackingCode
from wcs.data_sources import NamedDataSource
from wcs.wscalls import NamedWsCall
from wcs import fields
from wcs.logged_errors import LoggedError
from wcs.forms.root import PublicFormStatusPage
from utilities import get_app, login, create_temporary_pub, clean_temporary_pub
@ -2351,8 +2350,9 @@ def test_form_multi_page_post_edit(pub):
assert formdef.data_class().get(data_id).status == 'wf-%s' % st2.id
# jump to a nonexistent status == do not jump, but add a LoggedError
LoggedError.wipe()
assert LoggedError.count() == 0
if pub.is_using_postgresql():
pub.loggederror_class.wipe()
assert pub.loggederror_class.count() == 0
editable.status = 'deleted_status_id'
workflow.store()
# go back to st1
@ -2368,14 +2368,15 @@ def test_form_multi_page_post_edit(pub):
resp = resp.forms[0].submit('submit')
resp = resp.follow()
assert formdef.data_class().get(data_id).status == 'wf-%s' % st1.id # stay on st1
assert LoggedError.count() == 1
logged_error = LoggedError.select()[0]
assert logged_error.formdata_id == str(formdata.id)
assert logged_error.formdef_id == formdef.id
assert logged_error.workflow_id == workflow.id
assert logged_error.status_id == st1.id
assert logged_error.status_item_id == editable.id
assert logged_error.occurences_count == 1
if pub.is_using_postgresql():
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert logged_error.formdata_id == str(formdata.id)
assert logged_error.formdef_id == formdef.id
assert logged_error.workflow_id == workflow.id
assert logged_error.status_id == st1.id
assert logged_error.status_item_id == editable.id
assert logged_error.occurences_count == 1
# do it again: increment logged_error.occurences_count
page = login(get_app(pub), username='foo', password='foo').get('/test/%s/' % data_id)
@ -2386,9 +2387,10 @@ def test_form_multi_page_post_edit(pub):
resp = resp.forms[0].submit('submit')
resp = resp.follow()
assert formdef.data_class().get(data_id).status == 'wf-%s' % st1.id # stay on st1
assert LoggedError.count() == 1
logged_error = LoggedError.select()[0]
assert logged_error.occurences_count == 2
if pub.is_using_postgresql():
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert logged_error.occurences_count == 2
def test_form_edit_autocomplete_list(pub):
@ -6785,6 +6787,10 @@ def test_form_data_keywords(pub):
def test_logged_errors(pub):
if not pub.is_using_postgresql():
pytest.skip('this requires SQL')
return
Workflow.wipe()
workflow = Workflow.get_default_workflow()
workflow.id = '12'
@ -6806,29 +6812,29 @@ def test_logged_errors(pub):
formdef.fields = []
formdef.store()
LoggedError.wipe()
pub.loggederror_class.wipe()
app = get_app(pub)
resp = app.get('/test/')
resp = resp.form.submit('submit').follow()
resp = resp.form.submit('submit')
assert LoggedError.count() == 1
assert pub.loggederror_class.count() == 1
resp = app.get('/test/')
resp = resp.form.submit('submit').follow()
resp = resp.form.submit('submit')
assert LoggedError.count() == 1
assert pub.loggederror_class.count() == 1
error = LoggedError.get_on_index(
'34-12-just_submitted-_jump-failed-to-evaluate-condition-ZeroDivisionError-integer-division-or-modulo-by-zero',
'tech_id')
error = list(pub.loggederror_class.get_with_indexed_value(
'tech_id',
'34-12-just_submitted-_jump-failed-to-evaluate-condition-ZeroDivisionError-integer-division-or-modulo-by-zero'))[0]
assert error.occurences_count == 2
assert len(LoggedError.get_ids_with_indexed_value('formdef_id', '34')) == 1
assert len(LoggedError.get_ids_with_indexed_value('formdef_id', 'X')) == 0
assert len(list(pub.loggederror_class.get_with_indexed_value('formdef_id', '34'))) == 1
assert len(list(pub.loggederror_class.get_with_indexed_value('formdef_id', 'X'))) == 0
assert len(LoggedError.get_ids_with_indexed_value('workflow_id', '12')) == 1
assert len(LoggedError.get_ids_with_indexed_value('workflow_id', 'X')) == 0
assert len(list(pub.loggederror_class.get_with_indexed_value('workflow_id', '12'))) == 1
assert len(list(pub.loggederror_class.get_with_indexed_value('workflow_id', 'X'))) == 0
def test_formdata_named_wscall(http_requests, pub):
@ -8876,8 +8882,9 @@ def test_create_formdata_anonymous_submitted(create_formdata):
assert pq('.linked .foo_string').text() == 'zob'
def test_create_formdata_empty_item_ds_with_id_parameter(create_formdata):
LoggedError.wipe()
def test_create_formdata_empty_item_ds_with_id_parameter(pub, create_formdata):
if pub.is_using_postgresql():
pub.loggederror_class.wipe()
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {
@ -8905,9 +8912,11 @@ def test_create_formdata_empty_item_ds_with_id_parameter(create_formdata):
resp = resp.form.submit('submit') # -> submission
resp = resp.follow()
assert create_formdata['target_formdef'].data_class().count() == 0
assert LoggedError.count() == 0
if pub.is_using_postgresql():
assert pub.loggederror_class.count() == 0
resp = resp.form.submit('button_resubmit')
assert LoggedError.count() == 0
if pub.is_using_postgresql():
assert pub.loggederror_class.count() == 0
def test_js_libraries(pub):

View File

@ -18,7 +18,6 @@ from wcs.categories import Category
from wcs.conditions import Condition
from wcs.formdef import FormDef
from wcs.formdata import Evolution
from wcs.logged_errors import LoggedError
from wcs.roles import Role
from wcs import sessions
from wcs.variables import LazyFormData
@ -1058,12 +1057,14 @@ def test_lazy_formdata_queryset_filter(pub, variable_test_data):
assert queryset.count == 4
queryset = lazy_formdata.objects.filter_by('foo_foo').apply_filter_value('X')
assert queryset.count == 0
LoggedError.wipe()
if pub.is_using_postgresql():
pub.loggederror_class.wipe()
queryset = lazy_formdata.objects.filter_by('unknown').apply_filter_value('X')
assert queryset.count == 0
assert LoggedError.count() == 1
logged_error = LoggedError.select()[0]
assert logged_error.summary == 'Invalid filter "unknown"'
if pub.is_using_postgresql():
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert logged_error.summary == 'Invalid filter "unknown"'
# filter function on backoffice field
queryset = lazy_formdata.objects.filter_by('backoffice_blah').apply_filter_value('plop1')

View File

@ -8,7 +8,6 @@ from django.utils.encoding import force_bytes
from quixote import cleanup
from wcs.formdef import FormDef
from wcs.fields import FileField
from wcs.logged_errors import LoggedError
from wcs.mail_templates import MailTemplate
from wcs.workflows import Workflow, SendmailWorkflowStatusItem
from wcs.qommon.http_request import HTTPRequest
@ -193,7 +192,12 @@ def test_admin_workflow_edit(pub, superuser, mail_templates_option):
assert workflow.possible_status[0].items[0].mail_template == 'test-mail-template'
def test_workflow_send_mail_template(pub, superuser, mail_templates_option, emails):
def test_workflow_send_mail_template_with_sql(superuser, mail_templates_option, emails):
pub = create_temporary_pub(sql_mode=True)
req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
pub.set_app_dir(req)
pub._set_request(req)
Workflow.wipe()
MailTemplate.wipe()
@ -231,13 +235,13 @@ def test_workflow_send_mail_template(pub, superuser, mail_templates_option, emai
# check nothing is sent and an error is logged if the mail template is
# missing
emails.empty()
LoggedError.wipe()
pub.loggederror_class.wipe()
MailTemplate.wipe()
item.perform(formdata)
pub.get_request().response.process_after_jobs()
assert emails.count() == 0
assert LoggedError.count() == 1
logged_error = LoggedError.select()[0]
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert logged_error.summary == 'reference to invalid mail template test-mail-template in status Status1'

View File

@ -32,7 +32,6 @@ from wcs.fields import (StringField, DateField, MapField, FileField, ItemField,
ItemsField, CommentField, EmailField, PageField, TitleField,
SubtitleField, TextField, BoolField, TableField)
from wcs.formdata import Evolution
from wcs.logged_errors import LoggedError
from wcs.roles import Role
from wcs.workflows import (Workflow, WorkflowStatusItem,
SendmailWorkflowStatusItem, SendSMSWorkflowStatusItem,
@ -314,32 +313,36 @@ def test_jump_count_condition(pub):
assert item.must_jump(formdata) is False
def test_jump_bad_python_condition(pub):
def test_jump_bad_python_condition(two_pubs):
if not two_pubs.is_using_postgresql():
pytest.skip('this requires SQL')
return
FormDef.wipe()
formdef = FormDef()
formdef.name = 'foobar'
formdef.store()
pub.substitutions.feed(formdef)
two_pubs.substitutions.feed(formdef)
formdef.data_class().wipe()
formdata = formdef.data_class()()
item = JumpWorkflowStatusItem()
LoggedError.wipe()
two_pubs.loggederror_class.wipe()
item.condition = {'type': 'python', 'value': 'form_var_foobar == 0'}
assert item.must_jump(formdata) is False
assert LoggedError.count() == 1
logged_error = LoggedError.select()[0]
assert two_pubs.loggederror_class.count() == 1
logged_error = two_pubs.loggederror_class.select()[0]
assert logged_error.summary == 'Failed to evaluate condition'
assert logged_error.exception_class == 'NameError'
assert logged_error.exception_message == "name 'form_var_foobar' is not defined"
assert logged_error.expression == 'form_var_foobar == 0'
assert logged_error.expression_type == 'python'
LoggedError.wipe()
two_pubs.loggederror_class.wipe()
item.condition = {'type': 'python', 'value': '~ invalid ~'}
assert item.must_jump(formdata) is False
assert LoggedError.count() == 1
logged_error = LoggedError.select()[0]
assert two_pubs.loggederror_class.count() == 1
logged_error = two_pubs.loggederror_class.select()[0]
assert logged_error.summary == 'Failed to evaluate condition'
assert logged_error.exception_class == 'SyntaxError'
assert logged_error.exception_message == 'unexpected EOF while parsing (<string>, line 1)'
@ -347,7 +350,7 @@ def test_jump_bad_python_condition(pub):
assert logged_error.expression_type == 'python'
def test_jump_django_conditions(pub):
def test_jump_django_conditions(two_pubs):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'foobar'
@ -355,10 +358,11 @@ def test_jump_django_conditions(pub):
formdef.store()
formdata = formdef.data_class()()
formdata.data = {'1': 'hello'}
pub.substitutions.feed(formdata)
two_pubs.substitutions.feed(formdata)
item = JumpWorkflowStatusItem()
LoggedError.wipe()
if two_pubs.is_using_postgresql():
two_pubs.loggederror_class.wipe()
item.condition = {'type': 'django', 'value': '1 < 2'}
assert item.must_jump(formdata) is True
@ -371,17 +375,19 @@ def test_jump_django_conditions(pub):
item.condition = {'type': 'django', 'value': 'form_var_foo|first|upper == "X"'}
assert item.must_jump(formdata) is False
assert LoggedError.count() == 0
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 0
item.condition = {'type': 'django', 'value': '~ invalid ~'}
assert item.must_jump(formdata) is False
assert LoggedError.count() == 1
logged_error = LoggedError.select()[0]
assert logged_error.summary == 'Failed to evaluate condition'
assert logged_error.exception_class == 'TemplateSyntaxError'
assert logged_error.exception_message == "Could not parse the remainder: '~' from '~'"
assert logged_error.expression == '~ invalid ~'
assert logged_error.expression_type == 'django'
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 1
logged_error = two_pubs.loggederror_class.select()[0]
assert logged_error.summary == 'Failed to evaluate condition'
assert logged_error.exception_class == 'TemplateSyntaxError'
assert logged_error.exception_message == "Could not parse the remainder: '~' from '~'"
assert logged_error.expression == '~ invalid ~'
assert logged_error.expression_type == 'django'
def test_check_auth(pub):
@ -455,7 +461,8 @@ def test_dispatch(pub):
def test_dispatch_auto(pub):
LoggedError.wipe()
if pub.is_using_postgresql():
pub.loggederror_class.wipe()
formdef = FormDef()
formdef.name = 'baz'
@ -526,17 +533,19 @@ def test_dispatch_auto(pub):
pub.substitutions.feed(formdata)
item.perform(formdata)
assert not formdata.workflow_roles
assert LoggedError.count() == 1
error = LoggedError.select()[0]
assert error.tech_id == '%s-_default-error-in-dispatch-missing-role-foobar' % formdef.id
assert error.formdef_id == formdef.id
assert error.workflow_id == '_default'
assert error.summary == 'error in dispatch, missing role (foobar)'
assert error.occurences_count == 1
if pub.is_using_postgresql():
assert pub.loggederror_class.count() == 1
error = pub.loggederror_class.select()[0]
assert error.tech_id == '%s-_default-error-in-dispatch-missing-role-foobar' % formdef.id
assert error.formdef_id == formdef.id
assert error.workflow_id == '_default'
assert error.summary == 'error in dispatch, missing role (foobar)'
assert error.occurences_count == 1
def test_dispatch_computed(pub):
LoggedError.wipe()
if pub.is_using_postgresql():
pub.loggederror_class.wipe()
formdef = FormDef()
formdef.name = 'baz'
@ -571,13 +580,14 @@ def test_dispatch_computed(pub):
item.role_id = '="foobar"'
item.perform(formdata)
assert not formdata.workflow_roles
assert LoggedError.count() == 1
error = LoggedError.select()[0]
assert error.tech_id == '%s-_default-error-in-dispatch-missing-role-foobar' % formdef.id
assert error.formdef_id == formdef.id
assert error.workflow_id == '_default'
assert error.summary == 'error in dispatch, missing role (="foobar")'
assert error.occurences_count == 1
if pub.is_using_postgresql():
assert pub.loggederror_class.count() == 1
error = pub.loggederror_class.select()[0]
assert error.tech_id == '%s-_default-error-in-dispatch-missing-role-foobar' % formdef.id
assert error.formdef_id == formdef.id
assert error.workflow_id == '_default'
assert error.summary == 'error in dispatch, missing role (="foobar")'
assert error.occurences_count == 1
def test_roles(pub):
@ -2293,6 +2303,10 @@ def test_timeout_with_mark(two_pubs):
def test_jump_missing_previous_mark(two_pubs):
if not two_pubs.is_using_postgresql():
pytest.skip('this requires SQL')
return
workflow = Workflow(name='jump-mark')
st1 = workflow.add_status('Status1', 'st1')
@ -2317,9 +2331,9 @@ def test_jump_missing_previous_mark(two_pubs):
formdata.store()
time.sleep(0.3)
LoggedError.wipe()
two_pubs.loggederror_class.wipe()
_apply_timeouts(two_pubs)
assert LoggedError.count() == 1
assert two_pubs.loggederror_class.count() == 1
def test_sms(pub, sms_mocking):
@ -3745,7 +3759,8 @@ def test_profile(two_pubs):
def test_set_backoffice_field(http_requests, two_pubs):
Workflow.wipe()
FormDef.wipe()
LoggedError.wipe()
if two_pubs.is_using_postgresql():
two_pubs.loggederror_class.wipe()
wf = Workflow(name='xxx')
wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
wf.backoffice_fields_formdef.fields = [
@ -3844,32 +3859,36 @@ def test_set_backoffice_field(http_requests, two_pubs):
formdata = formdef.data_class().get(formdata.id)
assert formdata.data['bo1'] == ''
assert LoggedError.count() == 0
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 0
item.fields = [{'field_id': 'bo1', 'value': '= ~ invalid python ~'}]
item.perform(formdata)
formdata = formdef.data_class().get(formdata.id)
assert LoggedError.count() == 1
logged_error = LoggedError.select()[0]
assert logged_error.summary == 'Failed to compute Python expression'
assert logged_error.formdata_id == str(formdata.id)
assert logged_error.expression == ' ~ invalid python ~'
assert logged_error.expression_type == 'python'
assert logged_error.exception_class == 'SyntaxError'
assert logged_error.exception_message == 'invalid syntax (<string>, line 1)'
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 1
logged_error = two_pubs.loggederror_class.select()[0]
assert logged_error.summary == 'Failed to compute Python expression'
assert logged_error.formdata_id == str(formdata.id)
assert logged_error.expression == ' ~ invalid python ~'
assert logged_error.expression_type == 'python'
assert logged_error.exception_class == 'SyntaxError'
assert logged_error.exception_message == 'invalid syntax (<string>, line 1)'
LoggedError.wipe()
if two_pubs.is_using_postgresql():
two_pubs.loggederror_class.wipe()
item.fields = [{'field_id': 'bo1', 'value': '{% if bad django %}'}]
item.perform(formdata)
formdata = formdef.data_class().get(formdata.id)
assert LoggedError.count() == 1
logged_error = LoggedError.select()[0]
assert logged_error.summary == 'Failed to compute template'
assert logged_error.formdata_id == str(formdata.id)
assert logged_error.expression == '{% if bad django %}'
assert logged_error.expression_type == 'template'
assert logged_error.exception_class == 'TemplateError'
assert logged_error.exception_message.startswith('syntax error in Django template')
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 1
logged_error = two_pubs.loggederror_class.select()[0]
assert logged_error.summary == 'Failed to compute template'
assert logged_error.formdata_id == str(formdata.id)
assert logged_error.expression == '{% if bad django %}'
assert logged_error.expression_type == 'template'
assert logged_error.exception_class == 'TemplateError'
assert logged_error.exception_message.startswith('syntax error in Django template')
def test_set_backoffice_field_file(http_requests, two_pubs):
@ -4036,17 +4055,19 @@ def test_set_backoffice_field_file(http_requests, two_pubs):
item.parent = st1
item.fields = [{'field_id': 'bo1', 'value': value}]
LoggedError.wipe()
if two_pubs.is_using_postgresql():
two_pubs.loggederror_class.wipe()
item.perform(formdata)
formdata = formdef.data_class().get(formdata.id)
assert formdata.data['bo1'].base_filename == 'hello.txt'
assert formdata.data['bo1'].get_content() == b'HELLO WORLD'
assert LoggedError.count() == 1
logged_error = LoggedError.select()[0]
assert logged_error.summary.startswith('Failed to convert')
assert logged_error.formdata_id == str(formdata.id)
assert logged_error.exception_class == 'ValueError'
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 1
logged_error = two_pubs.loggederror_class.select()[0]
assert logged_error.summary.startswith('Failed to convert')
assert logged_error.formdata_id == str(formdata.id)
assert logged_error.exception_class == 'ValueError'
# check wrong field
item = SetBackofficeFieldsWorkflowStatusItem()
@ -4343,7 +4364,8 @@ def test_set_backoffice_field_items(two_pubs):
def test_set_backoffice_field_date(two_pubs):
Workflow.wipe()
FormDef.wipe()
LoggedError.wipe()
if two_pubs.is_using_postgresql():
two_pubs.loggederror_class.wipe()
wf = Workflow(name='xxx')
wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
st1 = wf.add_status('Status1')
@ -4391,18 +4413,21 @@ def test_set_backoffice_field_date(two_pubs):
assert datetime.date(*formdata.data['bo1'][:3]) == datetime.date(2017, 3, 23)
# invalid values => do nothing
assert LoggedError.count() == 0
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 0
for value in ('plop', '={}', '=[]'):
item = SetBackofficeFieldsWorkflowStatusItem()
item.parent = st1
item.fields = [{'field_id': 'bo1', 'value': value}]
LoggedError.wipe()
if two_pubs.is_using_postgresql():
two_pubs.loggederror_class.wipe()
item.perform(formdata)
formdata = formdef.data_class().get(formdata.id)
assert datetime.date(*formdata.data['bo1'][:3]) == datetime.date(2017, 3, 23)
assert LoggedError.count() == 1
assert LoggedError.select()[0].summary.startswith('Failed to convert')
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 1
assert two_pubs.loggederror_class.select()[0].summary.startswith('Failed to convert')
# None : empty date
item = SetBackofficeFieldsWorkflowStatusItem()
@ -4417,7 +4442,6 @@ def test_set_backoffice_field_date(two_pubs):
def test_set_backoffice_field_boolean(two_pubs):
Workflow.wipe()
FormDef.wipe()
LoggedError.wipe()
wf = Workflow(name='xxx')
wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
st1 = wf.add_status('Status1')
@ -4572,8 +4596,7 @@ def test_workflow_jump_condition_migration(pub):
def test_workflow_action_condition(two_pubs):
pub = two_pubs
pub._set_request(None) # to avoid after jobs
two_pubs._set_request(None) # to avoid after jobs
workflow = Workflow(name='jump condition migration')
st1 = workflow.add_status('Status1', 'st1')
workflow.store()
@ -4581,7 +4604,7 @@ def test_workflow_action_condition(two_pubs):
role = Role(name='bar1')
role.store()
user = pub.user_class()
user = two_pubs.user_class()
user.roles = [role.id]
user.store()
@ -4616,9 +4639,9 @@ def test_workflow_action_condition(two_pubs):
choice.condition = {'type': 'python', 'value': 'form_var_foo == "foo"'}
workflow.store()
with pub.substitutions.temporary_feed(formdata1):
with two_pubs.substitutions.temporary_feed(formdata1):
assert FormDef.get(formdef.id).data_class().get(formdata1.id).get_actions_roles() == set([role.id])
with pub.substitutions.temporary_feed(formdata2):
with two_pubs.substitutions.temporary_feed(formdata2):
assert FormDef.get(formdef.id).data_class().get(formdata2.id).get_actions_roles() == set()
assert len(FormDef.get(formdef.id).data_class().get_actionable_ids([role.id])) == 1
@ -4633,18 +4656,20 @@ def test_workflow_action_condition(two_pubs):
assert len(FormDef.get(formdef.id).data_class().get_actionable_ids([role.id])) == 2
# bad condition
LoggedError.wipe()
if two_pubs.is_using_postgresql():
two_pubs.loggederror_class.wipe()
choice.condition = {'type': 'python', 'value': 'foobar == barfoo'}
workflow.store()
assert len(FormDef.get(formdef.id).data_class().get_actionable_ids([role.id])) == 0
assert LoggedError.count() == 1
logged_error = LoggedError.select()[0]
assert logged_error.occurences_count > 1 # should be 2... == 12 with pickle, 4 with sql
assert logged_error.summary == 'Failed to evaluate condition'
assert logged_error.exception_class == 'NameError'
assert logged_error.exception_message == "name 'foobar' is not defined"
assert logged_error.expression == 'foobar == barfoo'
assert logged_error.expression_type == 'python'
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 1
logged_error = two_pubs.loggederror_class.select()[0]
assert logged_error.occurences_count > 1 # should be 2... == 12 with pickle, 4 with sql
assert logged_error.summary == 'Failed to evaluate condition'
assert logged_error.exception_class == 'NameError'
assert logged_error.exception_message == "name 'foobar' is not defined"
assert logged_error.expression == 'foobar == barfoo'
assert logged_error.expression_type == 'python'
def test_notifications(pub, http_requests):
@ -4774,6 +4799,7 @@ def test_aggregation_email(pub, emails):
formdef.fields = []
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
for i in range(5):
formdata = formdef.data_class()()
@ -4811,10 +4837,11 @@ def test_aggregation_email(pub, emails):
assert 'http://example.net/foobar/%s/status (New)' % formdata.id in emails.emails['New arrivals']['payload']
def test_create_formdata(pub):
def test_create_formdata(two_pubs):
FormDef.wipe()
LoggedError.wipe()
pub.tracking_code_class.wipe()
if two_pubs.is_using_postgresql():
two_pubs.loggederror_class.wipe()
two_pubs.tracking_code_class.wipe()
target_formdef = FormDef()
target_formdef.name = 'target form'
@ -4849,7 +4876,8 @@ def test_create_formdata(pub):
formdata.just_created()
assert target_formdef.data_class().count() == 0
assert LoggedError.count() == 0
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 0
# check unconfigure action do nothing
formdata.perform_workflow()
assert target_formdef.data_class().count() == 0
@ -4860,15 +4888,16 @@ def test_create_formdata(pub):
formdata.perform_workflow()
assert target_formdef.data_class().count() == 1
errors = LoggedError.select()
assert len(errors) == 2
assert any('form_var_toto_string' in (error.exception_message or '') for error in errors)
assert any('Missing field' in error.summary for error in errors)
if two_pubs.is_using_postgresql():
errors = two_pubs.loggederror_class.select()
assert len(errors) == 2
assert any('form_var_toto_string' in (error.exception_message or '') for error in errors)
assert any('Missing field' in error.summary for error in errors)
# no tracking code has been created
created_formdata = target_formdef.data_class().select()[0]
assert created_formdata.tracking_code is None
assert pub.tracking_code_class.count() == 0
assert two_pubs.tracking_code_class.count() == 0
# now we want one
target_formdef.enable_tracking_codes = True
target_formdef.store()
@ -4878,9 +4907,9 @@ def test_create_formdata(pub):
assert target_formdef.data_class().count() == 1
created_formdata = target_formdef.data_class().select()[0]
assert created_formdata.tracking_code is not None
assert pub.tracking_code_class.count() == 1
assert pub.tracking_code_class.select()[0].formdef_id == target_formdef.id
assert pub.tracking_code_class.select()[0].formdata_id == created_formdata.id
assert two_pubs.tracking_code_class.count() == 1
assert two_pubs.tracking_code_class.select()[0].formdef_id == target_formdef.id
assert two_pubs.tracking_code_class.select()[0].formdata_id == str(created_formdata.id)
create.condition = {'type': 'python', 'value': '1 == 2'}
wf.store()
@ -4891,10 +4920,11 @@ def test_create_formdata(pub):
assert target_formdef.data_class().count() == 0
def test_create_carddata(pub):
def test_create_carddata(two_pubs):
CardDef.wipe()
FormDef.wipe()
LoggedError.wipe()
if two_pubs.is_using_postgresql():
two_pubs.loggederror_class.wipe()
carddef = CardDef()
carddef.name = 'My card'
@ -4946,17 +4976,18 @@ def test_create_carddata(pub):
assert carddef.data_class().count() == 1
errors = LoggedError.select()
assert len(errors) == 2
assert any('form_var_undefined' in (error.exception_message or '') for error in errors)
assert any('invalid date value' in (error.exception_message or '') for error in errors)
if two_pubs.is_using_postgresql():
errors = two_pubs.loggederror_class.select()
assert len(errors) == 2
assert any('form_var_undefined' in (error.exception_message or '') for error in errors)
assert any('invalid date value' in (error.exception_message or '') for error in errors)
formdata = formdef.data_class()()
today = datetime.date.today()
formdata.data = {'1': 'item1',
'1_display': 'item1',
'2': today}
'2': today.timetuple()}
formdata.just_created()
formdata.perform_workflow()
@ -4974,10 +5005,11 @@ def test_create_carddata(pub):
assert carddef.data_class().count() == 0
def test_call_external_workflow_with_evolution_linked_object(pub):
def test_call_external_workflow_with_evolution_linked_object(two_pubs):
FormDef.wipe()
CardDef.wipe()
LoggedError.wipe()
if two_pubs.is_using_postgresql():
two_pubs.loggederror_class.wipe()
external_wf = Workflow(name='External Workflow')
st1 = external_wf.add_status(name='New')
@ -5056,17 +5088,19 @@ def test_call_external_workflow_with_evolution_linked_object(pub):
# remove external formdata
perform_items([action], formdata)
assert LoggedError.count() == 0
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 0
assert external_formdef.data_class().count() == 0
assert external_carddef.data_class().count() == 1
# formdata is already deleted: cannot find it again
perform_items([action], formdata)
assert LoggedError.count() == 1
logged_error = LoggedError.select()[0]
assert logged_error.summary == 'Could not find linked "External Form" object by id %s' % external_formdata.id
assert logged_error.exception_class == 'KeyError'
assert logged_error.status_item_id == action.id
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 1
logged_error = two_pubs.loggederror_class.select()[0]
assert logged_error.summary == 'Could not find linked "External Form" object by id %s' % external_formdata.id
assert logged_error.exception_class == 'KeyError'
assert logged_error.status_item_id == action.id
# try remove an unexisting carddef: do nothing
unused_carddef = CardDef()
@ -5074,26 +5108,30 @@ def test_call_external_workflow_with_evolution_linked_object(pub):
unused_carddef.fields = []
unused_carddef.workflow = external_wf
unused_carddef.store()
LoggedError.wipe()
if two_pubs.is_using_postgresql():
two_pubs.loggederror_class.wipe()
action.slug = 'carddef:%s' % unused_carddef.url_name
wf.store()
perform_items([action], formdata)
assert LoggedError.count() == 0
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 0
assert external_formdef.data_class().count() == 0
assert external_carddef.data_class().count() == 1
# remove the right carddef
action.slug = 'carddef:%s' % external_carddef.url_name
wf.store()
perform_items([action], formdata)
assert LoggedError.count() == 0
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 0
assert external_formdef.data_class().count() == 0
assert external_carddef.data_class().count() == 0
def test_call_external_workflow_with_data_sourced_object(pub):
def test_call_external_workflow_with_data_sourced_object(two_pubs):
FormDef.wipe()
CardDef.wipe()
LoggedError.wipe()
if two_pubs.is_using_postgresql():
two_pubs.loggederror_class.wipe()
carddef_wf = Workflow(name='Carddef Workflow')
carddef_wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(carddef_wf)
@ -5154,7 +5192,8 @@ def test_call_external_workflow_with_data_sourced_object(pub):
formdef.workflow = wf
formdef.store()
assert LoggedError.count() == 0
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 0
assert carddef.data_class().count() == 1
formdata = formdef.data_class()()
@ -5164,7 +5203,8 @@ def test_call_external_workflow_with_data_sourced_object(pub):
formdata.perform_workflow()
perform_items([update_action], formdata)
assert LoggedError.count() == 0
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 0
assert carddef.data_class().count() == 1
data = carddef.data_class().select()[0]
assert data.data['bo0'] == '1'
@ -5174,20 +5214,21 @@ def test_call_external_workflow_with_data_sourced_object(pub):
assert data.data['bo0'] == '2'
perform_items([delete_action], formdata)
assert LoggedError.count() == 0
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 0
assert carddef.data_class().count() == 0
perform_items([delete_action], formdata)
assert LoggedError.count() == 1
logged_error = LoggedError.select()[0]
assert logged_error.summary == 'Could not find linked "Data" object by id %s' % carddata.id
assert logged_error.exception_class == 'KeyError'
if two_pubs.is_using_postgresql():
assert two_pubs.loggederror_class.count() == 1
logged_error = two_pubs.loggederror_class.select()[0]
assert logged_error.summary == 'Could not find linked "Data" object by id %s' % carddata.id
assert logged_error.exception_class == 'KeyError'
def test_call_external_workflow_with_parent_object(pub):
FormDef.wipe()
CardDef.wipe()
LoggedError.wipe()
# carddef workflow, with global action to increment a counter in its
# backoffice fields.
@ -5274,7 +5315,6 @@ def test_call_external_workflow_with_parent_object(pub):
def test_call_external_workflow_use_caller_variable(pub):
FormDef.wipe()
CardDef.wipe()
LoggedError.wipe()
# carddef workflow, with global action to set a value in a backoffice field
carddef_wf = Workflow(name='Carddef Workflow')
@ -5349,7 +5389,6 @@ def test_call_external_workflow_use_caller_variable(pub):
def test_edit_carddata_with_data_sourced_object(pub):
FormDef.wipe()
CardDef.wipe()
LoggedError.wipe()
datasource = {
'type': 'formula',
@ -5462,7 +5501,6 @@ def test_edit_carddata_with_data_sourced_object(pub):
def test_edit_carddata_with_linked_object(pub):
FormDef.wipe()
CardDef.wipe()
LoggedError.wipe()
carddef = CardDef()
carddef.name = 'Parent'

View File

@ -3,7 +3,6 @@ import pytest
from wcs import fields
from wcs.formdef import FormDef
from wcs.logged_errors import LoggedError
from wcs.qommon.http_request import HTTPRequest
from wcs.qommon.template import Template
from wcs.wscalls import NamedWsCall
@ -27,6 +26,7 @@ def teardown_module(module):
def test_named_wscall(pub):
# create object
NamedWsCall.wipe()
wscall = NamedWsCall()
wscall.name = 'Hello'
wscall.request = {'url': 'http://example.net', 'qs_data': {'a': 'b'}}
@ -172,12 +172,13 @@ def test_webservice_delete(http_requests, pub):
@pytest.mark.parametrize('notify_on_errors', [True, False])
@pytest.mark.parametrize('record_on_errors', [True, False])
def test_webservice_on_error(http_requests, pub, emails, notify_on_errors, record_on_errors):
def test_webservice_on_error_with_sql(http_requests, emails, notify_on_errors, record_on_errors):
pub = create_temporary_pub(sql_mode=True)
pub.cfg['debug'] = {'error_email': 'errors@localhost.invalid'}
pub.write_cfg()
NamedWsCall.wipe()
LoggedError.wipe()
pub.loggederror_class.wipe()
FormDef.wipe()
wscall = NamedWsCall()
@ -200,7 +201,7 @@ def test_webservice_on_error(http_requests, pub, emails, notify_on_errors, recor
resp = get_app(pub).get('/foobar/')
assert 'Foo Bar ' in resp.text
assert emails.count() == 0
assert LoggedError.count() == 0
assert pub.loggederror_class.count() == 0
for url_part in ['400', '400-json', '404', '404-json', '500', 'json-err1', 'json-errheader1']:
status_code = 200
@ -217,7 +218,7 @@ def test_webservice_on_error(http_requests, pub, emails, notify_on_errors, recor
else:
assert emails.count() == 0
if record_on_errors:
assert LoggedError.count() == 1
LoggedError.wipe()
assert pub.loggederror_class.count() == 1
pub.loggederror_class.wipe()
else:
assert LoggedError.count() == 0
assert pub.loggederror_class.count() == 0

View File

@ -4,10 +4,8 @@ import os
import tempfile
import random
import psycopg2
import pytest
import shutil
import sys
import threading
from wcs import sql, sessions, custom_views
@ -20,8 +18,7 @@ from django.utils.six.moves.urllib import parse as urlparse
from wcs.qommon import force_str
import wcs
import wcs.wsgi
from wcs import publisher, compat
from wcs.qommon.http_request import HTTPRequest
from wcs import compat
from wcs.users import User
from wcs.tracking_code import TrackingCode
import wcs.qommon.emails
@ -81,13 +78,13 @@ def create_temporary_pub(sql_mode=False, templates_mode=False, lazy_mode=False):
pub.session_class = sql.Session
pub.custom_view_class = sql.CustomView
pub.snapshot_class = sql.Snapshot
pub.loggederror_class = sql.LoggedError
pub.is_using_postgresql = lambda: True
else:
pub.user_class = User
pub.tracking_code_class = TrackingCode
pub.session_class = sessions.BasicSession
pub.custom_view_class = custom_views.CustomView
pub.snapshot_class = None
pub.is_using_postgresql = lambda: False
pub.session_manager_class = sessions.StorageSessionManager
@ -172,6 +169,7 @@ def create_temporary_pub(sql_mode=False, templates_mode=False, lazy_mode=False):
sql.do_session_table()
sql.do_custom_views_table()
sql.do_snapshots_table()
sql.do_loggederrors_table()
sql.do_meta_table()
conn.close()

View File

@ -24,8 +24,6 @@ from wcs.qommon import _, ngettext, N_, template
from wcs.qommon import errors, get_cfg
from wcs.qommon.misc import localstrftime
from wcs.logged_errors import LoggedError
class LoggedErrorDirectory(Directory):
_q_exports = ['', 'delete', 'ack']
@ -107,7 +105,7 @@ class LoggedErrorDirectory(Directory):
return redirect('.')
def delete(self):
self.error.remove_self()
get_publisher().loggederror_class.remove_object(self.error.id)
return redirect('..')
@ -117,10 +115,14 @@ class LoggedErrorsDirectory(Directory):
@classmethod
def get_errors(cls, formdef_class=None, formdef_id=None, workflow_id=None):
errors = []
if not get_publisher().loggederror_class:
return errors
if formdef_id and formdef_class:
errors = [e for e in LoggedError.get_with_indexed_value('formdef_id', formdef_id) if e.formdef_class == formdef_class.__name__]
errors = [
e for e in get_publisher().loggederror_class.get_with_indexed_value('formdef_id', formdef_id)
if e.formdef_class == formdef_class.__name__]
elif workflow_id:
errors = LoggedError.get_with_indexed_value('workflow_id', workflow_id)
errors = get_publisher().loggederror_class.get_with_indexed_value('workflow_id', workflow_id)
return list(errors)
@classmethod
@ -168,7 +170,7 @@ class LoggedErrorsDirectory(Directory):
def _q_lookup(self, component):
try:
error = LoggedError.get(component)
error = get_publisher().loggederror_class.get(component)
except KeyError:
raise errors.TraversalError()
get_response().breadcrumb.append(('logged-errors/', _('Logged Errors')))

View File

@ -56,13 +56,13 @@ class Condition(object):
if self.log_errors:
get_logger().warning('failed to evaluate %r (%r)', self, e)
if self.record_errors:
from wcs.logged_errors import LoggedError
summary = _('Failed to evaluate condition')
LoggedError.record(summary,
formdata=self.context.get('formdata'),
status_item=self.context.get('status_item'),
expression=self.value, expression_type=self.type,
exception=e)
get_publisher().record_error(
summary,
formdata=self.context.get('formdata'),
status_item=self.context.get('status_item'),
expression=self.value, expression_type=self.type,
exception=e)
raise RuntimeError()
def evaluate_python(self, local_variables):

View File

@ -512,12 +512,11 @@ class NamedDataSource(XmlStorableObject):
url += param_name + '=' + urllib.quote(param_value)
def find_item(items, name, value):
from wcs.logged_errors import LoggedError
for item in items:
if str(item.get(name)) == str(value):
return item
# not found
LoggedError.record(_('Could not find element by id "%s"') % value)
get_publisher().record_error(_('Could not find element by id "%s"') % value)
return None
request = get_request()

View File

@ -387,8 +387,10 @@ class Field(object):
except TemplateError:
return ('', explicit_lock)
except AttributeError as e:
from wcs.logged_errors import LoggedError
LoggedError.record(_('Failed to evaluate prefill on field "%s"') % self.label, formdef=getattr(self, 'formdef', None), exception=e)
get_publisher().record_error(
_('Failed to evaluate prefill on field "%s"') % self.label,
formdef=getattr(self, 'formdef', None),
exception=e)
return ('', explicit_lock)
elif t == 'user' and user:

View File

@ -601,9 +601,8 @@ class FormData(StorableObject):
if status_id == '_previous':
previous_status = self.pop_previous_marked_status()
if not previous_status:
from wcs.logged_errors import LoggedError
summary = _('Failed to compute previous status')
LoggedError.record(summary, formdata=self)
get_publisher().record_error(summary, formdata=self)
return
status_id = previous_status.id
status = 'wf-%s' % status_id
@ -1214,7 +1213,6 @@ class FormData(StorableObject):
def iter_target_datas(self, objectdef=None, object_type=None, status_item=None):
# objectdef, object_type and status_item are provided when called from a workflow action
from wcs.wf.create_formdata import LinkedFormdataEvolutionPart
from wcs.logged_errors import LoggedError
from .carddef import CardDef
from .formdef import FormDef
@ -1268,8 +1266,9 @@ class FormData(StorableObject):
yield objectdef.data_class().get(target_id)
except KeyError as e:
# use custom error message depending on target type
LoggedError.record(_('Could not find linked "%(object_name)s" object by id %(object_id)s') % {
'object_name': objectdef.name, 'object_id': target_id},
get_publisher().record_error(
_('Could not find linked "%(object_name)s" object by id %(object_id)s') % {
'object_name': objectdef.name, 'object_id': target_id},
formdata=self, status_item=status_item, exception=e)
else:
# inspect page

View File

@ -17,18 +17,16 @@
import datetime
from .qommon.misc import simplify
from .qommon.xml_storage import XmlStorableObject
from wcs.carddef import CardDef
from wcs.formdef import FormDef
from wcs.workflows import Workflow
class LoggedError(XmlStorableObject):
class LoggedError(object):
_names = 'logged-errors'
xml_root_node = 'error'
_indexes = ['tech_id']
_hashed_indexes = ['formdef_id', 'workflow_id']
id = None
tech_id = None
summary = None
formdef_class = 'FormDef'
formdata_id = None
@ -46,19 +44,6 @@ class LoggedError(XmlStorableObject):
latest_occurence_timestamp = None
acked = False
# declarations for serialization
XML_NODES = [
('summary', 'str'), ('traceback', 'str'),
('exception_class', 'str'), ('exception_message', 'str'),
('expression', 'str'), ('expression_type', 'str'),
('formdata_id', 'str'), ('formdef_id', 'str'), ('workflow_id', 'str'),
('formdef_class', 'str'),
('status_id', 'str'), ('status_item_id', 'str'),
('occurences_count', 'int'),
('first_occurence_timestamp', 'datetime'),
('latest_occurence_timestamp', 'datetime'),
('acked', 'bool')]
@classmethod
def record(cls, error_summary, plain_error_msg=None, formdata=None,
formdef=None, workflow=None, status=None, status_item=None,
@ -96,13 +81,10 @@ class LoggedError(XmlStorableObject):
error.status_id = status.id
error.first_occurence_timestamp = datetime.datetime.now()
error.id = '%s-%s' % (
error.first_occurence_timestamp.strftime('%Y%m%d-%H%M%S'),
error.tech_id,
)
existing_error = cls.get_on_index(error.tech_id, 'tech_id', ignore_errors=True)
if existing_error:
error = existing_error
error.tech_id = error.build_tech_id()
existing_errors = list(cls.get_with_indexed_value('tech_id', error.tech_id))
if existing_errors:
error = existing_errors[0]
error.occurences_count += 1
error.latest_occurence_timestamp = datetime.datetime.now()
error.store()
@ -112,7 +94,7 @@ class LoggedError(XmlStorableObject):
def record_exception(cls, error_summary, plain_error_msg, publisher):
try:
context = publisher.substitutions.get_context_variables()
except Exception as e:
except Exception:
return
formdata_id = context.get('form_number_raw')
formdef_urlname = context.get('form_slug')
@ -125,11 +107,11 @@ class LoggedError(XmlStorableObject):
klass = CardDef
formdef = klass.get_by_urlname(formdef_urlname)
formdata = formdef.data_class().get(formdata_id, ignore_errors=True)
return cls.record(error_summary, plain_error_msg, formdata=formdata,
formdef=formdef, workflow=formdef.workflow)
return cls.record(
error_summary, plain_error_msg, formdata=formdata,
formdef=formdef, workflow=formdef.workflow)
@property
def tech_id(self):
def build_tech_id(self):
tech_id = ''
if self.formdef_id:
tech_id += '%s-' % self.formdef_id

View File

@ -52,7 +52,6 @@ from .qommon.cron import CronJob
from .users import User
from .tracking_code import TrackingCode
from .logged_errors import LoggedError
import pickle
@ -153,6 +152,7 @@ class WcsPublisher(StubWcsPublisher):
self.session_class = sql.Session
self.custom_view_class = sql.CustomView
self.snapshot_class = sql.Snapshot
self.loggederror_class = sql.LoggedError
sql.get_connection(new=True)
else:
self.user_class = User
@ -160,6 +160,7 @@ class WcsPublisher(StubWcsPublisher):
self.session_class = sessions.BasicSession
self.custom_view_class = custom_views.CustomView
self.snapshot_class = None
self.loggederror_class = None
self.session_manager_class = sessions.StorageSessionManager
self.set_session_manager(self.session_manager_class(session_class=self.session_class))
@ -312,6 +313,7 @@ class WcsPublisher(StubWcsPublisher):
sql.do_tracking_code_table()
sql.do_custom_views_table()
sql.do_snapshots_table()
sql.do_loggederrors_table()
sql.do_meta_table()
from .formdef import FormDef
from .carddef import CardDef
@ -340,8 +342,8 @@ class WcsPublisher(StubWcsPublisher):
def log_internal_error(self, error_summary, plain_error_msg, record=False, notify=True):
tech_id = None
if record:
logged_exception = LoggedError.record_exception(
if record and self.loggederror_class:
logged_exception = self.loggederror_class.record_exception(
error_summary, plain_error_msg, publisher=self)
if logged_exception:
tech_id = logged_exception.tech_id
@ -357,6 +359,10 @@ class WcsPublisher(StubWcsPublisher):
# this could happen on file descriptor exhaustion
pass
def record_error(self, *args, **kwargs):
if self.loggederror_class:
self.loggederror_class.record(*args, **kwargs)
def apply_global_action_timeouts(self):
from wcs.workflows import Workflow, WorkflowGlobalActionTimeoutTrigger
for workflow in Workflow.select():

View File

@ -45,6 +45,7 @@ import wcs.categories
import wcs.carddata
import wcs.custom_views
import wcs.formdata
import wcs.logged_errors
import wcs.snapshots
import wcs.tracking_code
import wcs.users
@ -859,6 +860,67 @@ def do_snapshots_table():
cur.close()
def do_loggederrors_table(concurrently=False):
conn, cur = get_connection_and_cursor()
table_name = 'loggederrors'
cur.execute('''SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = %s''', (table_name,))
if cur.fetchone()[0] == 0:
cur.execute('''CREATE TABLE %s (id SERIAL PRIMARY KEY,
tech_id VARCHAR UNIQUE,
summary VARCHAR,
formdef_class VARCHAR,
formdata_id VARCHAR,
formdef_id VARCHAR,
workflow_id VARCHAR,
status_id VARCHAR,
status_item_id VARCHAR,
expression VARCHAR,
expression_type VARCHAR,
traceback TEXT,
exception_class VARCHAR,
exception_message VARCHAR,
occurences_count INTEGER,
first_occurence_timestamp TIMESTAMP WITH TIME ZONE,
latest_occurence_timestamp TIMESTAMP WITH TIME ZONE,
acked BOOLEAN
)''' % table_name)
cur.execute('''SELECT column_name FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = %s''', (table_name,))
existing_fields = set([x[0] for x in cur.fetchall()])
needed_fields = set([x[0] for x in LoggedError._table_static_fields])
# delete obsolete fields
for field in (existing_fields - needed_fields):
cur.execute('''ALTER TABLE %s DROP COLUMN %s''' % (table_name, field))
create_index = 'CREATE INDEX'
if concurrently:
create_index = 'CREATE INDEX CONCURRENTLY'
# build indexes
existing_indexes = set()
cur.execute('''SELECT indexname
FROM pg_indexes
WHERE schemaname = 'public'
AND tablename = %s''', (table_name,))
existing_indexes = set([x[0] for x in cur.fetchall()])
for attr in ('formdef_id', 'workflow_id'):
if not table_name + '_' + attr + '_idx' in existing_indexes:
cur.execute('%(create_index)s %(table_name)s_%(attr)s_idx ON %(table_name)s (%(attr)s)' % {
'create_index': create_index,
'table_name': table_name,
'attr': attr})
conn.commit()
cur.close()
@guard_postgres
def do_meta_table(conn=None, cur=None, insert_current_sql_level=True):
own_conn = False
@ -2458,6 +2520,90 @@ class Snapshot(SqlMixin, wcs.snapshots.Snapshot):
return cls.get(row[0])
class LoggedError(SqlMixin, wcs.logged_errors.LoggedError):
_table_name = 'loggederrors'
_table_static_fields = [
('id', 'serial'),
('tech_id', 'varchar'),
('summary', 'varchar'),
('formdef_class', 'varchar'),
('formdata_id', 'varchar'),
('formdef_id', 'varchar'),
('workflow_id', 'varchar'),
('status_id', 'varchar'),
('status_item_id', 'varchar'),
('expression', 'varchar'),
('expression_type', 'varchar'),
('traceback', 'text'),
('exception_class', 'varchar'),
('exception_message', 'varchar'),
('occurences_count', 'integer'),
('first_occurence_timestamp', 'timestamptz'),
('latest_occurence_timestamp', 'timestamptz'),
('acked', 'boolean'),
]
_numerical_id = False
@guard_postgres
@invalidate_substitution_cache
def store(self):
sql_dict = {x: getattr(self, x) for x, y in self._table_static_fields}
conn, cur = get_connection_and_cursor()
if not self.id:
column_names = [x for x in sql_dict.keys() if x != 'id']
sql_statement = '''INSERT INTO %s (%s)
VALUES (%s)
RETURNING id''' % (
self._table_name,
', '.join(column_names),
', '.join(['%%(%s)s' % x for x in column_names]))
cur.execute(sql_statement, sql_dict)
self.id = cur.fetchone()[0]
else:
column_names = sql_dict.keys()
sql_statement = '''UPDATE %s SET %s WHERE id = %%(id)s RETURNING id''' % (
self._table_name,
', '.join(['%s = %%(%s)s' % (x, x) for x in column_names]))
cur.execute(sql_statement, sql_dict)
if cur.fetchone() is None:
raise AssertionError()
conn.commit()
cur.close()
@classmethod
def _row2ob(cls, row, **kwargs):
o = cls()
for field, value in zip(cls._table_static_fields, tuple(row)):
if field[1] in ('varchar', 'text'):
setattr(o, field[0], str_encode(value))
else:
setattr(o, field[0], value)
return o
@classmethod
def get_data_fields(cls):
return []
@classmethod
@guard_postgres
def fix_sequences(cls):
conn, cur = get_connection_and_cursor()
sql_statement = '''select max(id) from %s''' % cls._table_name
cur.execute(sql_statement)
max_id = cur.fetchone()[0]
if max_id is not None:
sql_statement = '''ALTER SEQUENCE %s_id_seq RESTART %s''' % (
cls._table_name, max_id+1)
cur.execute(sql_statement)
conn.commit()
cur.close()
class classproperty(object):
def __init__(self, f):
self.f = f
@ -2716,7 +2862,7 @@ def get_period_total(period_start=None, include_start=True, period_end=None, inc
# latest migration, number + description (description is not used
# programmaticaly but will make sure git conflicts if two migrations are
# separately added with the same number)
SQL_LEVEL = (46, 'add index on formdata(status) - fix')
SQL_LEVEL = (47, 'use SQL to store LoggedError')
def migrate_global_views(conn, cur):
@ -2871,6 +3017,9 @@ def migrate():
if sql_level < 42:
# 42: create snapshots table
do_snapshots_table()
if sql_level < 47:
# 47: store LoggedErrors in SQL
do_loggederrors_table()
cur.execute('''UPDATE wcs_meta SET value = %s WHERE key = %s''', (
str(SQL_LEVEL[0]), 'sql_level'))

View File

@ -24,7 +24,6 @@ from quixote import get_publisher, get_request
from pyproj import Geod
from .logged_errors import LoggedError
from .qommon import misc, force_str, _
from .qommon.evalutils import make_datetime
from .qommon.templatetags.qommon import parse_datetime
@ -169,7 +168,7 @@ class LazyFormDefObjectsManager(object):
from wcs import sql
criteria = Equal(sql.get_field_id(field), value)
return self._clone(self._criterias + [criteria])
LoggedError.record(_('Invalid filter "%s"') % self.pending_attr, formdata=self._formdata)
get_publisher().record_error(_('Invalid filter "%s"') % self.pending_attr, formdata=self._formdata)
return self.none()
def __getattr__(self, attribute):

View File

@ -15,6 +15,8 @@
# along with this program; if not, see <http://www.gnu.org/licenses/>.
import xml.etree.ElementTree as ET
from quixote import get_publisher
from quixote.html import htmltext
from ..qommon import _, N_
@ -133,17 +135,17 @@ class SetBackofficeFieldsWorkflowStatusItem(WorkflowStatusItem):
try:
new_value = formdef_field.convert_value_from_anything(new_value)
except ValueError as e:
from wcs.logged_errors import LoggedError
summary = _('Failed to convert %(class)s value to %(kind)s field (%(id)s)') % {
'class': type(new_value),
'kind': _(getattr(formdef_field, 'description', 'unknown')),
'id': field['field_id'],
}
expression_dict = self.get_expression(field['value'])
LoggedError.record(summary, formdata=formdata, status_item=self,
expression=expression_dict['value'],
expression_type=expression_dict['type'],
exception=e)
get_publisher().record_error(
summary, formdata=formdata, status_item=self,
expression=expression_dict['value'],
expression_type=expression_dict['type'],
exception=e)
continue
formdata.data['%s' % field['field_id']] = new_value

View File

@ -27,7 +27,6 @@ from wcs.qommon.form import (WidgetListAsTable, CompositeWidget,
SingleSelectWidget, ComputedExpressionWidget,
CheckboxWidget, VarnameWidget, HtmlWidget)
from wcs.logged_errors import LoggedError
from wcs.workflows import WorkflowStatusItem, register_item_class
from wcs.formdef import FormDef
@ -391,8 +390,9 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
field=dest_field,
value=src.data.get(field.id))
except Exception as e:
LoggedError.record(_('Could not copy field by varname for "%s"') % field.varname,
formdata=src, status_item=self, exception=e)
get_publisher().record_error(
_('Could not copy field by varname for "%s"') % field.varname,
formdata=src, status_item=self, exception=e)
# field.id can be serialized to xml, so we must always convert them to
# str when matching
@ -418,14 +418,15 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
value=value)
except Exception as e:
expression = self.get_expression(mapping.expression)
LoggedError.record(_('Could not assign value to field "%s"') % dest_field.label,
formdata=src, status_item=self,
expression=expression['value'], expression_type=expression['type'],
exception=e)
get_publisher().record_error(
_('Could not assign value to field "%s"') % dest_field.label,
formdata=src, status_item=self,
expression=expression['value'], expression_type=expression['type'],
exception=e)
if missing_fields:
summary = _('Missing field %r') % missing_fields
LoggedError.record(summary, formdata=src, status_item=self)
get_publisher().record_error(summary, formdata=src, status_item=self)
def _set_value(self, formdata, field, value):
if field.convert_value_from_anything:

View File

@ -188,7 +188,6 @@ class DispatchWorkflowStatusItem(WorkflowStatusItem):
return htmltext('<ul class="rules">%s</ul>') % htmltext('').join(result)
def perform(self, formdata):
from wcs.logged_errors import LoggedError
if not formdata.workflow_roles:
formdata.workflow_roles = {}
@ -199,7 +198,7 @@ class DispatchWorkflowStatusItem(WorkflowStatusItem):
return
new_role_id = self.get_computed_role_id(self.role_id)
if not new_role_id:
LoggedError.record(_('error in dispatch, missing role (%s)') % self.role_id, formdata=formdata)
get_publisher().record_error(_('error in dispatch, missing role (%s)') % self.role_id, formdata=formdata)
elif self.dispatch_type == 'automatic':
if not (self.role_key and self.variable and self.rules):
return
@ -226,7 +225,7 @@ class DispatchWorkflowStatusItem(WorkflowStatusItem):
if new_role_id:
if not Role.has_key(new_role_id):
LoggedError.record(_('error in dispatch, missing role (%s)') % new_role_id, formdata=formdata)
get_publisher().record_error(_('error in dispatch, missing role (%s)') % new_role_id, formdata=formdata)
else:
formdata.workflow_roles[self.role_key] = str(new_role_id)
formdata.store()

View File

@ -19,7 +19,6 @@ from quixote import get_publisher
from wcs.qommon import _
from wcs.qommon.form import SingleSelectWidget
from wcs.logged_errors import LoggedError
from wcs.workflows import WorkflowStatusItem, perform_items, register_item_class
from wcs.workflows import WorkflowGlobalActionWebserviceTrigger, Workflow
from wcs.carddef import CardDef
@ -126,8 +125,9 @@ class ExternalWorkflowGlobalAction(WorkflowStatusItem):
trigger = self.get_trigger(objectdef.workflow)
if not trigger:
LoggedError.record(_('No trigger with id "%s" found in workflow') % self.trigger_id,
formdata=formdata, status_item=self)
get_publisher().record_error(
_('No trigger with id "%s" found in workflow') % self.trigger_id,
formdata=formdata, status_item=self)
return
class CallerSource:

View File

@ -447,13 +447,12 @@ class WebserviceCallStatusItem(WorkflowStatusItem):
try:
target = self.parent.parent.get_status(value)
except KeyError:
from wcs.logged_errors import LoggedError
message = _('reference to invalid status in workflow %(workflow)s, status %(status)s, item %(item)s') % {
'workflow': self.parent.parent.name,
'status': self.parent.name,
'item': self.description,
}
LoggedError.record(message, workflow=self.parent.parent)
get_publisher().record_error(message, workflow=self.parent.parent)
continue
targets.append(target)
return targets

View File

@ -1982,15 +1982,15 @@ class WorkflowStatusItem(XmlSerialisable):
vars.update(context or {})
def log_exception(exception):
from wcs.logged_errors import LoggedError
if expression['type'] == 'template':
summary = _('Failed to compute template')
else:
summary = _('Failed to compute Python expression')
LoggedError.record(summary, formdata=formdata, status_item=status_item,
expression=expression['value'],
expression_type=expression['type'],
exception=exception)
get_publisher().record_error(
summary, formdata=formdata, status_item=status_item,
expression=expression['value'],
expression_type=expression['type'],
exception=exception)
if expression['type'] == 'template':
try:
@ -2043,14 +2043,13 @@ class WorkflowStatusItem(XmlSerialisable):
targets = [x for x in self.parent.parent.possible_status if x.id == self.status]
if not targets and formdata: # do not log in presentation context: formdata is needed
from wcs.logged_errors import LoggedError
message = _('reference to invalid status %(target)s in status %(status)s, '
'action %(status_item)s') % {
'target': self.status,
'status': self.parent.name,
'status_item': _(self.description)
}
LoggedError.record(message, formdata=formdata, status_item=self)
get_publisher().record_error(message, formdata=formdata, status_item=self)
return targets
@ -2677,12 +2676,11 @@ class SendmailWorkflowStatusItem(WorkflowStatusItem):
subject = mail_template.subject
extra_attachments = mail_template.attachments
else:
from wcs.logged_errors import LoggedError
message = _('reference to invalid mail template %(mail_template)s in status %(status)s') % {
'status': self.parent.name,
'mail_template': self.mail_template,
}
LoggedError.record(message, formdata=formdata, status_item=self)
get_publisher().record_error(message, formdata=formdata, status_item=self)
return
try: