Projet

Général

Profil

0001-misc-store-LoggedErrors-in-SQL-48925.patch

Lauréline Guérin, 11 décembre 2020 14:51

Télécharger (81,3 ko)

Voir les différences:

Subject: [PATCH] misc: store LoggedErrors in SQL (#48925)

 tests/admin_pages/test_workflow.py      |  13 +-
 tests/backoffice_pages/test_all.py      |  51 +++--
 tests/backoffice_pages/test_carddata.py |  11 +-
 tests/form_pages/test_all.py            |  65 +++---
 tests/test_formdata.py                  |  11 +-
 tests/test_mail_templates.py            |  14 +-
 tests/test_workflows.py                 | 290 ++++++++++++++----------
 tests/test_wscall.py                    |  15 +-
 tests/utilities.py                      |   8 +-
 wcs/admin/logged_errors.py              |  14 +-
 wcs/conditions.py                       |  12 +-
 wcs/data_sources.py                     |   3 +-
 wcs/fields.py                           |   6 +-
 wcs/formdata.py                         |   9 +-
 wcs/logged_errors.py                    |  42 +---
 wcs/publisher.py                        |  12 +-
 wcs/sql.py                              | 151 +++++++++++-
 wcs/variables.py                        |   3 +-
 wcs/wf/backoffice_fields.py             |  12 +-
 wcs/wf/create_formdata.py               |  17 +-
 wcs/wf/dispatch.py                      |   5 +-
 wcs/wf/external_workflow.py             |   6 +-
 wcs/wf/wscall.py                        |   3 +-
 wcs/workflows.py                        |  16 +-
 24 files changed, 497 insertions(+), 292 deletions(-)
tests/admin_pages/test_workflow.py
12 12
from webtest import Upload
13 13

  
14 14
from wcs.qommon.http_request import HTTPRequest
15
from wcs.logged_errors import LoggedError
16 15
from wcs.roles import Role
17 16
from wcs.workflows import (
18 17
    Workflow, WorkflowCriticalityLevel, DisplayMessageWorkflowStatusItem,
......
1779 1778

  
1780 1779

  
1781 1780
def test_workflows_wscall_status_error(pub):
1781
    if not pub.is_using_postgresql():
1782
        pytest.skip('this requires SQL')
1783
        return
1784

  
1782 1785
    create_superuser(pub)
1783 1786

  
1784
    LoggedError.wipe()
1787
    pub.loggederror_class.wipe()
1785 1788
    Workflow.wipe()
1786 1789
    workflow = Workflow(name='foo')
1787 1790
    baz_status = workflow.add_status(name='baz')
......
1798 1801

  
1799 1802
    app = login(get_app(pub))
1800 1803
    app.get('/backoffice/workflows/%s/' % workflow.id)
1801
    assert LoggedError.count() == 0
1804
    assert pub.loggederror_class.count() == 0
1802 1805

  
1803 1806
    # delete foo status
1804 1807
    del workflow.possible_status[1]
1805 1808
    workflow.store()
1806 1809
    app.get('/backoffice/workflows/%s/' % workflow.id)
1807
    assert LoggedError.count() == 1
1808
    error = LoggedError.select()[0]
1810
    assert pub.loggederror_class.count() == 1
1811
    error = pub.loggederror_class.select()[0]
1809 1812
    assert error.tech_id == '%s-reference-to-invalid-status-in-workflow-foo-status-baz-item-webservice' % workflow.id
1810 1813
    assert error.formdef_id is None
1811 1814
    assert error.workflow_id == workflow.id
tests/backoffice_pages/test_all.py
39 39
from wcs.carddef import CardDef
40 40
from wcs.categories import Category
41 41
from wcs.formdef import FormDef
42
from wcs.logged_errors import LoggedError
43 42
from wcs import fields
44 43
from wcs.wscalls import NamedWsCall
45 44

  
......
2257 2256
@pytest.mark.parametrize('notify_on_errors', [True, False])
2258 2257
@pytest.mark.parametrize('record_on_errors', [True, False])
2259 2258
def test_backoffice_wscall_on_error(http_requests, pub, emails, notify_on_errors, record_on_errors):
2259
    if not pub.is_using_postgresql():
2260
        pytest.skip('this requires SQL')
2261
        return
2262

  
2260 2263
    pub.cfg['debug'] = {'error_email': 'errors@localhost.invalid'}
2261 2264
    pub.write_cfg()
2262 2265

  
2263 2266
    create_user(pub)
2264 2267
    create_environment(pub)
2268
    pub.loggederror_class.wipe()
2265 2269
    formdef = FormDef.get_by_urlname('form-title')
2266 2270
    form_class = formdef.data_class()
2267 2271

  
......
2314 2318
    else:
2315 2319
        assert emails.count() == 0
2316 2320

  
2317
    # check LoggedError
2321
    # check pub.loggederror_class
2318 2322
    if record_on_errors:
2319
        assert LoggedError.count() == 1
2320
        LoggedError.wipe()
2323
        assert pub.loggederror_class.count() == 1
2324
        pub.loggederror_class.wipe()
2321 2325
    else:
2322
        assert LoggedError.count() == 0
2326
        assert pub.loggederror_class.count() == 0
2323 2327

  
2324 2328

  
2325 2329
def test_backoffice_wscall_attachment(http_requests, pub):
......
4709 4713

  
4710 4714

  
4711 4715
def test_backoffice_logged_errors(pub):
4716
    if not pub.is_using_postgresql():
4717
        pytest.skip('this requires SQL')
4718
        return
4719

  
4712 4720
    Workflow.wipe()
4713 4721
    workflow = Workflow.get_default_workflow()
4714 4722
    workflow.id = '12'
......
4738 4746
    carddef.fields = []
4739 4747
    carddef.store()
4740 4748

  
4741
    LoggedError.wipe()
4749
    pub.loggederror_class.wipe()
4742 4750

  
4743 4751
    create_superuser(pub)
4744 4752
    app = login(get_app(pub))
......
4753 4761
    resp = app.get('/test/')
4754 4762
    resp = resp.form.submit('submit').follow()
4755 4763
    resp = resp.form.submit('submit')
4756
    assert LoggedError.count() == 1
4764
    assert pub.loggederror_class.count() == 1
4757 4765

  
4758 4766
    app = login(get_app(pub))
4759 4767
    resp = app.get('/backoffice/forms/%s/' % formdef.id)
......
4773 4781
    assert not 'Acked' in resp.text
4774 4782
    resp = resp.click('Ack').follow()
4775 4783
    assert 'Acked' in resp.text
4776
    assert LoggedError.select()[0].acked is True
4784
    assert pub.loggederror_class.select()[0].acked is True
4777 4785
    resp = resp.click('Delete').follow()
4778
    assert LoggedError.count() == 0
4786
    assert pub.loggederror_class.count() == 0
4779 4787

  
4780 4788
    pub.cfg.update({'debug': {'error_email': None}})
4781 4789
    pub.write_cfg()
......
4784 4792
    resp = app.get('/test/')
4785 4793
    resp = resp.form.submit('submit').follow()
4786 4794
    resp = resp.form.submit('submit')
4787
    assert LoggedError.count() == 1
4795
    assert pub.loggederror_class.count() == 1
4788 4796

  
4789 4797
    app = login(get_app(pub))
4790 4798
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
......
4802 4810
    resp = app.get('/test/')
4803 4811
    resp = resp.form.submit('submit').follow()
4804 4812
    resp = resp.form.submit('submit')
4805
    assert LoggedError.count() == 2
4813
    assert pub.loggederror_class.count() == 2
4806 4814

  
4807 4815
    app = login(get_app(pub))
4808 4816
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
......
5338 5346
    source_formdef.store()
5339 5347
    source_formdef.data_class().wipe()
5340 5348
    target_formdef.data_class().wipe()
5341
    LoggedError.wipe()
5349
    if pub.is_using_postgresql():
5350
        pub.loggederror_class.wipe()
5342 5351
    return locals()
5343 5352

  
5344 5353

  
5345
def test_backoffice_create_formdata_backoffice_submission(create_formdata):
5354
def test_backoffice_create_formdata_backoffice_submission(pub, create_formdata):
5346 5355
    # create submitting user
5347 5356
    user = create_formdata['pub'].user_class()
5348 5357
    user.name = 'Jean Darmette'
......
5373 5382
    assert target_data_class.count() == 0
5374 5383
    # resubmit it through backoffice submission
5375 5384
    resp = resp.form.submit(name='button_resubmit')
5376
    assert LoggedError.count() == 0
5385
    if pub.is_using_postgresql():
5386
        assert pub.loggederror_class.count() == 0
5377 5387
    assert target_data_class.count() == 1
5378 5388
    target_formdata = target_data_class.select()[0]
5379 5389

  
......
5402 5412
    assert pq('.field-type-file .value').text() == 'bar'
5403 5413

  
5404 5414

  
5405
def test_linked_forms_variables(create_formdata):
5415
def test_linked_forms_variables(pub, create_formdata):
5406 5416
    # create source formdata
5407 5417
    formdata = create_formdata['source_formdef'].data_class()()
5408 5418
    upload = PicklableUpload('/foo/bar', content_type='text/plain')
......
5419 5429
    formdata.perform_workflow()
5420 5430
    formdata.store()
5421 5431

  
5422
    get_publisher().substitutions.reset()
5423
    get_publisher().substitutions.feed(formdata)
5424
    substvars = get_publisher().substitutions.get_context_variables(mode='lazy')
5432
    pub.substitutions.reset()
5433
    pub.substitutions.feed(formdata)
5434
    substvars = pub.substitutions.get_context_variables(mode='lazy')
5425 5435
    assert str(substvars['form_links_resubmitted_form_var_foo_string']) == 'coucou'
5426 5436
    assert 'form_links_resubmitted_form_var_foo_string' in substvars.get_flat_keys()
5427 5437

  
......
5440 5450
    assert 'form_links_resubmitted_form_var_foo_string' not in resp
5441 5451

  
5442 5452

  
5443
def test_backoffice_create_formdata_map_fields_by_varname(create_formdata):
5453
def test_backoffice_create_formdata_map_fields_by_varname(pub, create_formdata):
5444 5454
    create_formdata['create_formdata'].map_fields_by_varname = True
5445 5455
    create_formdata['create_formdata'].mappings  = []
5446 5456
    create_formdata['wf'].store()
......
5490 5500
    assert target_data_class.count() == 0
5491 5501
    # resubmit it through backoffice submission
5492 5502
    resp = resp.form.submit(name='button_resubmit')
5493
    assert LoggedError.count() == 0
5503
    if pub.is_using_postgresql():
5504
        assert pub.loggederror_class.count() == 0
5494 5505
    assert target_data_class.count() == 1
5495 5506
    target_formdata = target_data_class.select()[0]
5496 5507

  
tests/backoffice_pages/test_carddata.py
10 10
from wcs.carddef import CardDef
11 11
from wcs.categories import CardDefCategory
12 12
from wcs.formdef import FormDef
13
from wcs.logged_errors import LoggedError
14 13
from wcs.qommon.http_request import HTTPRequest
15 14
from wcs.wf.wscall import WebserviceCallStatusItem
16 15
from wcs.workflows import ChoiceWorkflowStatusItem, Workflow
......
417 416

  
418 417

  
419 418
def test_backoffice_cards_wscall_failure_display(http_requests, pub, studio):
420
    LoggedError.wipe()
419
    if not pub.is_using_postgresql():
420
        pytest.skip('this requires SQL')
421
        return
422

  
423
    pub.loggederror_class.wipe()
421 424
    user = create_user(pub)
422 425

  
423 426
    Workflow.wipe()
......
473 476
    resp = resp.follow()
474 477
    assert 'Error during webservice call' in resp.text
475 478

  
476
    assert LoggedError.count() == 1
477
    assert LoggedError.select()[0].get_formdata().data == {'1': 'plop'}
479
    assert pub.loggederror_class.count() == 1
480
    assert pub.loggederror_class.select()[0].get_formdata().data == {'1': 'plop'}
478 481

  
479 482

  
480 483
def test_block_card_item_link(pub, studio, blocks_feature):
tests/form_pages/test_all.py
50 50
from wcs.data_sources import NamedDataSource
51 51
from wcs.wscalls import NamedWsCall
52 52
from wcs import fields
53
from wcs.logged_errors import LoggedError
54 53
from wcs.forms.root import PublicFormStatusPage
55 54

  
56 55
from utilities import get_app, login, create_temporary_pub, clean_temporary_pub
......
2351 2350
    assert formdef.data_class().get(data_id).status == 'wf-%s' % st2.id
2352 2351

  
2353 2352
    # jump to a nonexistent status == do not jump, but add a LoggedError
2354
    LoggedError.wipe()
2355
    assert LoggedError.count() == 0
2353
    if pub.is_using_postgresql():
2354
        pub.loggederror_class.wipe()
2355
        assert pub.loggederror_class.count() == 0
2356 2356
    editable.status = 'deleted_status_id'
2357 2357
    workflow.store()
2358 2358
    # go back to st1
......
2368 2368
    resp = resp.forms[0].submit('submit')
2369 2369
    resp = resp.follow()
2370 2370
    assert formdef.data_class().get(data_id).status == 'wf-%s' % st1.id  # stay on st1
2371
    assert LoggedError.count() == 1
2372
    logged_error = LoggedError.select()[0]
2373
    assert logged_error.formdata_id == str(formdata.id)
2374
    assert logged_error.formdef_id == formdef.id
2375
    assert logged_error.workflow_id == workflow.id
2376
    assert logged_error.status_id == st1.id
2377
    assert logged_error.status_item_id == editable.id
2378
    assert logged_error.occurences_count == 1
2371
    if pub.is_using_postgresql():
2372
        assert pub.loggederror_class.count() == 1
2373
        logged_error = pub.loggederror_class.select()[0]
2374
        assert logged_error.formdata_id == str(formdata.id)
2375
        assert logged_error.formdef_id == formdef.id
2376
        assert logged_error.workflow_id == workflow.id
2377
        assert logged_error.status_id == st1.id
2378
        assert logged_error.status_item_id == editable.id
2379
        assert logged_error.occurences_count == 1
2379 2380

  
2380 2381
    # do it again: increment logged_error.occurences_count
2381 2382
    page = login(get_app(pub), username='foo', password='foo').get('/test/%s/' % data_id)
......
2386 2387
    resp = resp.forms[0].submit('submit')
2387 2388
    resp = resp.follow()
2388 2389
    assert formdef.data_class().get(data_id).status == 'wf-%s' % st1.id  # stay on st1
2389
    assert LoggedError.count() == 1
2390
    logged_error = LoggedError.select()[0]
2391
    assert logged_error.occurences_count == 2
2390
    if pub.is_using_postgresql():
2391
        assert pub.loggederror_class.count() == 1
2392
        logged_error = pub.loggederror_class.select()[0]
2393
        assert logged_error.occurences_count == 2
2392 2394

  
2393 2395

  
2394 2396
def test_form_edit_autocomplete_list(pub):
......
6785 6787

  
6786 6788

  
6787 6789
def test_logged_errors(pub):
6790
    if not pub.is_using_postgresql():
6791
        pytest.skip('this requires SQL')
6792
        return
6793

  
6788 6794
    Workflow.wipe()
6789 6795
    workflow = Workflow.get_default_workflow()
6790 6796
    workflow.id = '12'
......
6806 6812
    formdef.fields = []
6807 6813
    formdef.store()
6808 6814

  
6809
    LoggedError.wipe()
6815
    pub.loggederror_class.wipe()
6810 6816

  
6811 6817
    app = get_app(pub)
6812 6818
    resp = app.get('/test/')
6813 6819
    resp = resp.form.submit('submit').follow()
6814 6820
    resp = resp.form.submit('submit')
6815
    assert LoggedError.count() == 1
6821
    assert pub.loggederror_class.count() == 1
6816 6822

  
6817 6823
    resp = app.get('/test/')
6818 6824
    resp = resp.form.submit('submit').follow()
6819 6825
    resp = resp.form.submit('submit')
6820
    assert LoggedError.count() == 1
6826
    assert pub.loggederror_class.count() == 1
6821 6827

  
6822
    error = LoggedError.get_on_index(
6823
        '34-12-just_submitted-_jump-failed-to-evaluate-condition-ZeroDivisionError-integer-division-or-modulo-by-zero',
6824
        'tech_id')
6828
    error = list(pub.loggederror_class.get_with_indexed_value(
6829
        'tech_id',
6830
        '34-12-just_submitted-_jump-failed-to-evaluate-condition-ZeroDivisionError-integer-division-or-modulo-by-zero'))[0]
6825 6831
    assert error.occurences_count == 2
6826 6832

  
6827
    assert len(LoggedError.get_ids_with_indexed_value('formdef_id', '34')) == 1
6828
    assert len(LoggedError.get_ids_with_indexed_value('formdef_id', 'X')) == 0
6833
    assert len(list(pub.loggederror_class.get_with_indexed_value('formdef_id', '34'))) == 1
6834
    assert len(list(pub.loggederror_class.get_with_indexed_value('formdef_id', 'X'))) == 0
6829 6835

  
6830
    assert len(LoggedError.get_ids_with_indexed_value('workflow_id', '12')) == 1
6831
    assert len(LoggedError.get_ids_with_indexed_value('workflow_id', 'X')) == 0
6836
    assert len(list(pub.loggederror_class.get_with_indexed_value('workflow_id', '12'))) == 1
6837
    assert len(list(pub.loggederror_class.get_with_indexed_value('workflow_id', 'X'))) == 0
6832 6838

  
6833 6839

  
6834 6840
def test_formdata_named_wscall(http_requests, pub):
......
8876 8882
    assert pq('.linked .foo_string').text() == 'zob'
8877 8883

  
8878 8884

  
8879
def test_create_formdata_empty_item_ds_with_id_parameter(create_formdata):
8880
    LoggedError.wipe()
8885
def test_create_formdata_empty_item_ds_with_id_parameter(pub, create_formdata):
8886
    if pub.is_using_postgresql():
8887
        pub.loggederror_class.wipe()
8881 8888
    NamedDataSource.wipe()
8882 8889
    data_source = NamedDataSource(name='foobar')
8883 8890
    data_source.data_source = {
......
8905 8912
        resp = resp.form.submit('submit')  # -> submission
8906 8913
        resp = resp.follow()
8907 8914
        assert create_formdata['target_formdef'].data_class().count() == 0
8908
        assert LoggedError.count() == 0
8915
        if pub.is_using_postgresql():
8916
            assert pub.loggederror_class.count() == 0
8909 8917
        resp = resp.form.submit('button_resubmit')
8910
        assert LoggedError.count() == 0
8918
        if pub.is_using_postgresql():
8919
            assert pub.loggederror_class.count() == 0
8911 8920

  
8912 8921

  
8913 8922
def test_js_libraries(pub):
tests/test_formdata.py
18 18
from wcs.conditions import Condition
19 19
from wcs.formdef import FormDef
20 20
from wcs.formdata import Evolution
21
from wcs.logged_errors import LoggedError
22 21
from wcs.roles import Role
23 22
from wcs import sessions
24 23
from wcs.variables import LazyFormData
......
1058 1057
    assert queryset.count == 4
1059 1058
    queryset = lazy_formdata.objects.filter_by('foo_foo').apply_filter_value('X')
1060 1059
    assert queryset.count == 0
1061
    LoggedError.wipe()
1060
    if pub.is_using_postgresql():
1061
        pub.loggederror_class.wipe()
1062 1062
    queryset = lazy_formdata.objects.filter_by('unknown').apply_filter_value('X')
1063 1063
    assert queryset.count == 0
1064
    assert LoggedError.count() == 1
1065
    logged_error = LoggedError.select()[0]
1066
    assert logged_error.summary == 'Invalid filter "unknown"'
1064
    if pub.is_using_postgresql():
1065
        assert pub.loggederror_class.count() == 1
1066
        logged_error = pub.loggederror_class.select()[0]
1067
        assert logged_error.summary == 'Invalid filter "unknown"'
1067 1068

  
1068 1069
    # filter function on backoffice field
1069 1070
    queryset = lazy_formdata.objects.filter_by('backoffice_blah').apply_filter_value('plop1')
tests/test_mail_templates.py
8 8
from quixote import cleanup
9 9
from wcs.formdef import FormDef
10 10
from wcs.fields import FileField
11
from wcs.logged_errors import LoggedError
12 11
from wcs.mail_templates import MailTemplate
13 12
from wcs.workflows import Workflow, SendmailWorkflowStatusItem
14 13
from wcs.qommon.http_request import HTTPRequest
......
193 192
    assert workflow.possible_status[0].items[0].mail_template == 'test-mail-template'
194 193

  
195 194

  
196
def test_workflow_send_mail_template(pub, superuser, mail_templates_option, emails):
195
def test_workflow_send_mail_template_with_sql(superuser, mail_templates_option, emails):
196
    pub = create_temporary_pub(sql_mode=True)
197
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
198
    pub.set_app_dir(req)
199
    pub._set_request(req)
200

  
197 201
    Workflow.wipe()
198 202
    MailTemplate.wipe()
199 203

  
......
231 235
    # check nothing is sent and an error is logged if the mail template is
232 236
    # missing
233 237
    emails.empty()
234
    LoggedError.wipe()
238
    pub.loggederror_class.wipe()
235 239
    MailTemplate.wipe()
236 240
    item.perform(formdata)
237 241
    pub.get_request().response.process_after_jobs()
238 242
    assert emails.count() == 0
239
    assert LoggedError.count() == 1
240
    logged_error = LoggedError.select()[0]
243
    assert pub.loggederror_class.count() == 1
244
    logged_error = pub.loggederror_class.select()[0]
241 245
    assert logged_error.summary == 'reference to invalid mail template test-mail-template in status Status1'
242 246

  
243 247

  
tests/test_workflows.py
32 32
        ItemsField, CommentField, EmailField, PageField, TitleField,
33 33
        SubtitleField, TextField, BoolField, TableField)
34 34
from wcs.formdata import Evolution
35
from wcs.logged_errors import LoggedError
36 35
from wcs.roles import Role
37 36
from wcs.workflows import (Workflow, WorkflowStatusItem,
38 37
        SendmailWorkflowStatusItem, SendSMSWorkflowStatusItem,
......
314 313
    assert item.must_jump(formdata) is False
315 314

  
316 315

  
317
def test_jump_bad_python_condition(pub):
316
def test_jump_bad_python_condition(two_pubs):
317
    if not two_pubs.is_using_postgresql():
318
        pytest.skip('this requires SQL')
319
        return
320

  
318 321
    FormDef.wipe()
319 322
    formdef = FormDef()
320 323
    formdef.name = 'foobar'
321 324
    formdef.store()
322
    pub.substitutions.feed(formdef)
325
    two_pubs.substitutions.feed(formdef)
323 326
    formdef.data_class().wipe()
324 327
    formdata = formdef.data_class()()
325 328
    item = JumpWorkflowStatusItem()
326 329

  
327
    LoggedError.wipe()
330
    two_pubs.loggederror_class.wipe()
328 331
    item.condition = {'type': 'python', 'value': 'form_var_foobar == 0'}
329 332
    assert item.must_jump(formdata) is False
330
    assert LoggedError.count() == 1
331
    logged_error = LoggedError.select()[0]
333
    assert two_pubs.loggederror_class.count() == 1
334
    logged_error = two_pubs.loggederror_class.select()[0]
332 335
    assert logged_error.summary == 'Failed to evaluate condition'
333 336
    assert logged_error.exception_class == 'NameError'
334 337
    assert logged_error.exception_message == "name 'form_var_foobar' is not defined"
335 338
    assert logged_error.expression == 'form_var_foobar == 0'
336 339
    assert logged_error.expression_type == 'python'
337 340

  
338
    LoggedError.wipe()
341
    two_pubs.loggederror_class.wipe()
339 342
    item.condition = {'type': 'python', 'value': '~ invalid ~'}
340 343
    assert item.must_jump(formdata) is False
341
    assert LoggedError.count() == 1
342
    logged_error = LoggedError.select()[0]
344
    assert two_pubs.loggederror_class.count() == 1
345
    logged_error = two_pubs.loggederror_class.select()[0]
343 346
    assert logged_error.summary == 'Failed to evaluate condition'
344 347
    assert logged_error.exception_class == 'SyntaxError'
345 348
    assert logged_error.exception_message == 'unexpected EOF while parsing (<string>, line 1)'
......
347 350
    assert logged_error.expression_type == 'python'
348 351

  
349 352

  
350
def test_jump_django_conditions(pub):
353
def test_jump_django_conditions(two_pubs):
351 354
    FormDef.wipe()
352 355
    formdef = FormDef()
353 356
    formdef.name = 'foobar'
......
355 358
    formdef.store()
356 359
    formdata = formdef.data_class()()
357 360
    formdata.data = {'1': 'hello'}
358
    pub.substitutions.feed(formdata)
361
    two_pubs.substitutions.feed(formdata)
359 362
    item = JumpWorkflowStatusItem()
360 363

  
361
    LoggedError.wipe()
364
    if two_pubs.is_using_postgresql():
365
        two_pubs.loggederror_class.wipe()
362 366
    item.condition = {'type': 'django', 'value': '1 < 2'}
363 367
    assert item.must_jump(formdata) is True
364 368

  
......
371 375
    item.condition = {'type': 'django', 'value': 'form_var_foo|first|upper == "X"'}
372 376
    assert item.must_jump(formdata) is False
373 377

  
374
    assert LoggedError.count() == 0
378
    if two_pubs.is_using_postgresql():
379
        assert two_pubs.loggederror_class.count() == 0
375 380

  
376 381
    item.condition = {'type': 'django', 'value': '~ invalid ~'}
377 382
    assert item.must_jump(formdata) is False
378
    assert LoggedError.count() == 1
379
    logged_error = LoggedError.select()[0]
380
    assert logged_error.summary == 'Failed to evaluate condition'
381
    assert logged_error.exception_class == 'TemplateSyntaxError'
382
    assert logged_error.exception_message == "Could not parse the remainder: '~' from '~'"
383
    assert logged_error.expression == '~ invalid ~'
384
    assert logged_error.expression_type == 'django'
383
    if two_pubs.is_using_postgresql():
384
        assert two_pubs.loggederror_class.count() == 1
385
        logged_error = two_pubs.loggederror_class.select()[0]
386
        assert logged_error.summary == 'Failed to evaluate condition'
387
        assert logged_error.exception_class == 'TemplateSyntaxError'
388
        assert logged_error.exception_message == "Could not parse the remainder: '~' from '~'"
389
        assert logged_error.expression == '~ invalid ~'
390
        assert logged_error.expression_type == 'django'
385 391

  
386 392

  
387 393
def test_check_auth(pub):
......
455 461

  
456 462

  
457 463
def test_dispatch_auto(pub):
458
    LoggedError.wipe()
464
    if pub.is_using_postgresql():
465
        pub.loggederror_class.wipe()
459 466

  
460 467
    formdef = FormDef()
461 468
    formdef.name = 'baz'
......
526 533
    pub.substitutions.feed(formdata)
527 534
    item.perform(formdata)
528 535
    assert not formdata.workflow_roles
529
    assert LoggedError.count() == 1
530
    error = LoggedError.select()[0]
531
    assert error.tech_id == '%s-_default-error-in-dispatch-missing-role-foobar' % formdef.id
532
    assert error.formdef_id == formdef.id
533
    assert error.workflow_id == '_default'
534
    assert error.summary == 'error in dispatch, missing role (foobar)'
535
    assert error.occurences_count == 1
536
    if pub.is_using_postgresql():
537
        assert pub.loggederror_class.count() == 1
538
        error = pub.loggederror_class.select()[0]
539
        assert error.tech_id == '%s-_default-error-in-dispatch-missing-role-foobar' % formdef.id
540
        assert error.formdef_id == formdef.id
541
        assert error.workflow_id == '_default'
542
        assert error.summary == 'error in dispatch, missing role (foobar)'
543
        assert error.occurences_count == 1
536 544

  
537 545

  
538 546
def test_dispatch_computed(pub):
539
    LoggedError.wipe()
547
    if pub.is_using_postgresql():
548
        pub.loggederror_class.wipe()
540 549

  
541 550
    formdef = FormDef()
542 551
    formdef.name = 'baz'
......
571 580
    item.role_id = '="foobar"'
572 581
    item.perform(formdata)
573 582
    assert not formdata.workflow_roles
574
    assert LoggedError.count() == 1
575
    error = LoggedError.select()[0]
576
    assert error.tech_id == '%s-_default-error-in-dispatch-missing-role-foobar' % formdef.id
577
    assert error.formdef_id == formdef.id
578
    assert error.workflow_id == '_default'
579
    assert error.summary == 'error in dispatch, missing role (="foobar")'
580
    assert error.occurences_count == 1
583
    if pub.is_using_postgresql():
584
        assert pub.loggederror_class.count() == 1
585
        error = pub.loggederror_class.select()[0]
586
        assert error.tech_id == '%s-_default-error-in-dispatch-missing-role-foobar' % formdef.id
587
        assert error.formdef_id == formdef.id
588
        assert error.workflow_id == '_default'
589
        assert error.summary == 'error in dispatch, missing role (="foobar")'
590
        assert error.occurences_count == 1
581 591

  
582 592

  
583 593
def test_roles(pub):
......
2293 2303

  
2294 2304

  
2295 2305
def test_jump_missing_previous_mark(two_pubs):
2306
    if not two_pubs.is_using_postgresql():
2307
        pytest.skip('this requires SQL')
2308
        return
2309

  
2296 2310
    workflow = Workflow(name='jump-mark')
2297 2311
    st1 = workflow.add_status('Status1', 'st1')
2298 2312

  
......
2317 2331
    formdata.store()
2318 2332

  
2319 2333
    time.sleep(0.3)
2320
    LoggedError.wipe()
2334
    two_pubs.loggederror_class.wipe()
2321 2335
    _apply_timeouts(two_pubs)
2322
    assert LoggedError.count() == 1
2336
    assert two_pubs.loggederror_class.count() == 1
2323 2337

  
2324 2338

  
2325 2339
def test_sms(pub, sms_mocking):
......
3745 3759
def test_set_backoffice_field(http_requests, two_pubs):
3746 3760
    Workflow.wipe()
3747 3761
    FormDef.wipe()
3748
    LoggedError.wipe()
3762
    if two_pubs.is_using_postgresql():
3763
        two_pubs.loggederror_class.wipe()
3749 3764
    wf = Workflow(name='xxx')
3750 3765
    wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
3751 3766
    wf.backoffice_fields_formdef.fields = [
......
3844 3859
    formdata = formdef.data_class().get(formdata.id)
3845 3860
    assert formdata.data['bo1'] == ''
3846 3861

  
3847
    assert LoggedError.count() == 0
3862
    if two_pubs.is_using_postgresql():
3863
        assert two_pubs.loggederror_class.count() == 0
3848 3864

  
3849 3865
    item.fields = [{'field_id': 'bo1', 'value': '= ~ invalid python ~'}]
3850 3866
    item.perform(formdata)
3851 3867
    formdata = formdef.data_class().get(formdata.id)
3852
    assert LoggedError.count() == 1
3853
    logged_error = LoggedError.select()[0]
3854
    assert logged_error.summary == 'Failed to compute Python expression'
3855
    assert logged_error.formdata_id == str(formdata.id)
3856
    assert logged_error.expression == ' ~ invalid python ~'
3857
    assert logged_error.expression_type == 'python'
3858
    assert logged_error.exception_class == 'SyntaxError'
3859
    assert logged_error.exception_message == 'invalid syntax (<string>, line 1)'
3868
    if two_pubs.is_using_postgresql():
3869
        assert two_pubs.loggederror_class.count() == 1
3870
        logged_error = two_pubs.loggederror_class.select()[0]
3871
        assert logged_error.summary == 'Failed to compute Python expression'
3872
        assert logged_error.formdata_id == str(formdata.id)
3873
        assert logged_error.expression == ' ~ invalid python ~'
3874
        assert logged_error.expression_type == 'python'
3875
        assert logged_error.exception_class == 'SyntaxError'
3876
        assert logged_error.exception_message == 'invalid syntax (<string>, line 1)'
3860 3877

  
3861
    LoggedError.wipe()
3878
    if two_pubs.is_using_postgresql():
3879
        two_pubs.loggederror_class.wipe()
3862 3880
    item.fields = [{'field_id': 'bo1', 'value': '{% if bad django %}'}]
3863 3881
    item.perform(formdata)
3864 3882
    formdata = formdef.data_class().get(formdata.id)
3865
    assert LoggedError.count() == 1
3866
    logged_error = LoggedError.select()[0]
3867
    assert logged_error.summary == 'Failed to compute template'
3868
    assert logged_error.formdata_id == str(formdata.id)
3869
    assert logged_error.expression == '{% if bad django %}'
3870
    assert logged_error.expression_type == 'template'
3871
    assert logged_error.exception_class == 'TemplateError'
3872
    assert logged_error.exception_message.startswith('syntax error in Django template')
3883
    if two_pubs.is_using_postgresql():
3884
        assert two_pubs.loggederror_class.count() == 1
3885
        logged_error = two_pubs.loggederror_class.select()[0]
3886
        assert logged_error.summary == 'Failed to compute template'
3887
        assert logged_error.formdata_id == str(formdata.id)
3888
        assert logged_error.expression == '{% if bad django %}'
3889
        assert logged_error.expression_type == 'template'
3890
        assert logged_error.exception_class == 'TemplateError'
3891
        assert logged_error.exception_message.startswith('syntax error in Django template')
3873 3892

  
3874 3893

  
3875 3894
def test_set_backoffice_field_file(http_requests, two_pubs):
......
4036 4055
        item.parent = st1
4037 4056
        item.fields = [{'field_id': 'bo1', 'value': value}]
4038 4057

  
4039
        LoggedError.wipe()
4058
        if two_pubs.is_using_postgresql():
4059
            two_pubs.loggederror_class.wipe()
4040 4060
        item.perform(formdata)
4041 4061

  
4042 4062
        formdata = formdef.data_class().get(formdata.id)
4043 4063
        assert formdata.data['bo1'].base_filename == 'hello.txt'
4044 4064
        assert formdata.data['bo1'].get_content() == b'HELLO WORLD'
4045
        assert LoggedError.count() == 1
4046
        logged_error = LoggedError.select()[0]
4047
        assert logged_error.summary.startswith('Failed to convert')
4048
        assert logged_error.formdata_id == str(formdata.id)
4049
        assert logged_error.exception_class == 'ValueError'
4065
        if two_pubs.is_using_postgresql():
4066
            assert two_pubs.loggederror_class.count() == 1
4067
            logged_error = two_pubs.loggederror_class.select()[0]
4068
            assert logged_error.summary.startswith('Failed to convert')
4069
            assert logged_error.formdata_id == str(formdata.id)
4070
            assert logged_error.exception_class == 'ValueError'
4050 4071

  
4051 4072
    # check wrong field
4052 4073
    item = SetBackofficeFieldsWorkflowStatusItem()
......
4343 4364
def test_set_backoffice_field_date(two_pubs):
4344 4365
    Workflow.wipe()
4345 4366
    FormDef.wipe()
4346
    LoggedError.wipe()
4367
    if two_pubs.is_using_postgresql():
4368
        two_pubs.loggederror_class.wipe()
4347 4369
    wf = Workflow(name='xxx')
4348 4370
    wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
4349 4371
    st1 = wf.add_status('Status1')
......
4391 4413
    assert datetime.date(*formdata.data['bo1'][:3]) == datetime.date(2017, 3, 23)
4392 4414

  
4393 4415
    # invalid values => do nothing
4394
    assert LoggedError.count() == 0
4416
    if two_pubs.is_using_postgresql():
4417
        assert two_pubs.loggederror_class.count() == 0
4395 4418
    for value in ('plop', '={}', '=[]'):
4396 4419
        item = SetBackofficeFieldsWorkflowStatusItem()
4397 4420
        item.parent = st1
4398 4421
        item.fields = [{'field_id': 'bo1', 'value': value}]
4399 4422

  
4400
        LoggedError.wipe()
4423
        if two_pubs.is_using_postgresql():
4424
            two_pubs.loggederror_class.wipe()
4401 4425
        item.perform(formdata)
4402 4426
        formdata = formdef.data_class().get(formdata.id)
4403 4427
        assert datetime.date(*formdata.data['bo1'][:3]) == datetime.date(2017, 3, 23)
4404
        assert LoggedError.count() == 1
4405
        assert LoggedError.select()[0].summary.startswith('Failed to convert')
4428
        if two_pubs.is_using_postgresql():
4429
            assert two_pubs.loggederror_class.count() == 1
4430
            assert two_pubs.loggederror_class.select()[0].summary.startswith('Failed to convert')
4406 4431

  
4407 4432
    # None : empty date
4408 4433
    item = SetBackofficeFieldsWorkflowStatusItem()
......
4417 4442
def test_set_backoffice_field_boolean(two_pubs):
4418 4443
    Workflow.wipe()
4419 4444
    FormDef.wipe()
4420
    LoggedError.wipe()
4421 4445
    wf = Workflow(name='xxx')
4422 4446
    wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
4423 4447
    st1 = wf.add_status('Status1')
......
4572 4596

  
4573 4597

  
4574 4598
def test_workflow_action_condition(two_pubs):
4575
    pub = two_pubs
4576
    pub._set_request(None)  # to avoid after jobs
4599
    two_pubs._set_request(None)  # to avoid after jobs
4577 4600
    workflow = Workflow(name='jump condition migration')
4578 4601
    st1 = workflow.add_status('Status1', 'st1')
4579 4602
    workflow.store()
......
4581 4604
    role = Role(name='bar1')
4582 4605
    role.store()
4583 4606

  
4584
    user = pub.user_class()
4607
    user = two_pubs.user_class()
4585 4608
    user.roles = [role.id]
4586 4609
    user.store()
4587 4610

  
......
4616 4639
    choice.condition = {'type': 'python', 'value': 'form_var_foo == "foo"'}
4617 4640
    workflow.store()
4618 4641

  
4619
    with pub.substitutions.temporary_feed(formdata1):
4642
    with two_pubs.substitutions.temporary_feed(formdata1):
4620 4643
        assert FormDef.get(formdef.id).data_class().get(formdata1.id).get_actions_roles() == set([role.id])
4621
    with pub.substitutions.temporary_feed(formdata2):
4644
    with two_pubs.substitutions.temporary_feed(formdata2):
4622 4645
        assert FormDef.get(formdef.id).data_class().get(formdata2.id).get_actions_roles() == set()
4623 4646

  
4624 4647
    assert len(FormDef.get(formdef.id).data_class().get_actionable_ids([role.id])) == 1
......
4633 4656
    assert len(FormDef.get(formdef.id).data_class().get_actionable_ids([role.id])) == 2
4634 4657

  
4635 4658
    # bad condition
4636
    LoggedError.wipe()
4659
    if two_pubs.is_using_postgresql():
4660
        two_pubs.loggederror_class.wipe()
4637 4661
    choice.condition = {'type': 'python', 'value': 'foobar == barfoo'}
4638 4662
    workflow.store()
4639 4663
    assert len(FormDef.get(formdef.id).data_class().get_actionable_ids([role.id])) == 0
4640
    assert LoggedError.count() == 1
4641
    logged_error = LoggedError.select()[0]
4642
    assert logged_error.occurences_count > 1  # should be 2... == 12 with pickle, 4 with sql
4643
    assert logged_error.summary == 'Failed to evaluate condition'
4644
    assert logged_error.exception_class == 'NameError'
4645
    assert logged_error.exception_message == "name 'foobar' is not defined"
4646
    assert logged_error.expression == 'foobar == barfoo'
4647
    assert logged_error.expression_type == 'python'
4664
    if two_pubs.is_using_postgresql():
4665
        assert two_pubs.loggederror_class.count() == 1
4666
        logged_error = two_pubs.loggederror_class.select()[0]
4667
        assert logged_error.occurences_count > 1  # should be 2... == 12 with pickle, 4 with sql
4668
        assert logged_error.summary == 'Failed to evaluate condition'
4669
        assert logged_error.exception_class == 'NameError'
4670
        assert logged_error.exception_message == "name 'foobar' is not defined"
4671
        assert logged_error.expression == 'foobar == barfoo'
4672
        assert logged_error.expression_type == 'python'
4648 4673

  
4649 4674

  
4650 4675
def test_notifications(pub, http_requests):
......
4774 4799
    formdef.fields = []
4775 4800
    formdef.workflow_id = workflow.id
4776 4801
    formdef.store()
4802
    formdef.data_class().wipe()
4777 4803

  
4778 4804
    for i in range(5):
4779 4805
        formdata = formdef.data_class()()
......
4811 4837
    assert 'http://example.net/foobar/%s/status (New)' % formdata.id in emails.emails['New arrivals']['payload']
4812 4838

  
4813 4839

  
4814
def test_create_formdata(pub):
4840
def test_create_formdata(two_pubs):
4815 4841
    FormDef.wipe()
4816
    LoggedError.wipe()
4817
    pub.tracking_code_class.wipe()
4842
    if two_pubs.is_using_postgresql():
4843
        two_pubs.loggederror_class.wipe()
4844
    two_pubs.tracking_code_class.wipe()
4818 4845

  
4819 4846
    target_formdef = FormDef()
4820 4847
    target_formdef.name = 'target form'
......
4849 4876
    formdata.just_created()
4850 4877

  
4851 4878
    assert target_formdef.data_class().count() == 0
4852
    assert LoggedError.count() == 0
4879
    if two_pubs.is_using_postgresql():
4880
        assert two_pubs.loggederror_class.count() == 0
4853 4881
    # check unconfigure action do nothing
4854 4882
    formdata.perform_workflow()
4855 4883
    assert target_formdef.data_class().count() == 0
......
4860 4888
    formdata.perform_workflow()
4861 4889
    assert target_formdef.data_class().count() == 1
4862 4890

  
4863
    errors = LoggedError.select()
4864
    assert len(errors) == 2
4865
    assert any('form_var_toto_string' in (error.exception_message or '') for error in errors)
4866
    assert any('Missing field' in error.summary for error in errors)
4891
    if two_pubs.is_using_postgresql():
4892
        errors = two_pubs.loggederror_class.select()
4893
        assert len(errors) == 2
4894
        assert any('form_var_toto_string' in (error.exception_message or '') for error in errors)
4895
        assert any('Missing field' in error.summary for error in errors)
4867 4896

  
4868 4897
    # no tracking code has been created
4869 4898
    created_formdata = target_formdef.data_class().select()[0]
4870 4899
    assert created_formdata.tracking_code is None
4871
    assert pub.tracking_code_class.count() == 0
4900
    assert two_pubs.tracking_code_class.count() == 0
4872 4901
    # now we want one
4873 4902
    target_formdef.enable_tracking_codes = True
4874 4903
    target_formdef.store()
......
4878 4907
    assert target_formdef.data_class().count() == 1
4879 4908
    created_formdata = target_formdef.data_class().select()[0]
4880 4909
    assert created_formdata.tracking_code is not None
4881
    assert pub.tracking_code_class.count() == 1
4882
    assert pub.tracking_code_class.select()[0].formdef_id == target_formdef.id
4883
    assert pub.tracking_code_class.select()[0].formdata_id == created_formdata.id
4910
    assert two_pubs.tracking_code_class.count() == 1
4911
    assert two_pubs.tracking_code_class.select()[0].formdef_id == target_formdef.id
4912
    assert two_pubs.tracking_code_class.select()[0].formdata_id == str(created_formdata.id)
4884 4913

  
4885 4914
    create.condition = {'type': 'python', 'value': '1 == 2'}
4886 4915
    wf.store()
......
4891 4920
    assert target_formdef.data_class().count() == 0
4892 4921

  
4893 4922

  
4894
def test_create_carddata(pub):
4923
def test_create_carddata(two_pubs):
4895 4924
    CardDef.wipe()
4896 4925
    FormDef.wipe()
4897
    LoggedError.wipe()
4926
    if two_pubs.is_using_postgresql():
4927
        two_pubs.loggederror_class.wipe()
4898 4928

  
4899 4929
    carddef = CardDef()
4900 4930
    carddef.name = 'My card'
......
4946 4976

  
4947 4977
    assert carddef.data_class().count() == 1
4948 4978

  
4949
    errors = LoggedError.select()
4950
    assert len(errors) == 2
4951
    assert any('form_var_undefined' in (error.exception_message or '') for error in errors)
4952
    assert any('invalid date value' in (error.exception_message or '') for error in errors)
4979
    if two_pubs.is_using_postgresql():
4980
        errors = two_pubs.loggederror_class.select()
4981
        assert len(errors) == 2
4982
        assert any('form_var_undefined' in (error.exception_message or '') for error in errors)
4983
        assert any('invalid date value' in (error.exception_message or '') for error in errors)
4953 4984

  
4954 4985
    formdata = formdef.data_class()()
4955 4986
    today = datetime.date.today()
4956 4987

  
4957 4988
    formdata.data = {'1': 'item1',
4958 4989
                     '1_display': 'item1',
4959
                     '2': today}
4990
                     '2': today.timetuple()}
4960 4991
    formdata.just_created()
4961 4992
    formdata.perform_workflow()
4962 4993

  
......
4974 5005
    assert carddef.data_class().count() == 0
4975 5006

  
4976 5007

  
4977
def test_call_external_workflow_with_evolution_linked_object(pub):
5008
def test_call_external_workflow_with_evolution_linked_object(two_pubs):
4978 5009
    FormDef.wipe()
4979 5010
    CardDef.wipe()
4980
    LoggedError.wipe()
5011
    if two_pubs.is_using_postgresql():
5012
        two_pubs.loggederror_class.wipe()
4981 5013

  
4982 5014
    external_wf = Workflow(name='External Workflow')
4983 5015
    st1 = external_wf.add_status(name='New')
......
5056 5088

  
5057 5089
    # remove external formdata
5058 5090
    perform_items([action], formdata)
5059
    assert LoggedError.count() == 0
5091
    if two_pubs.is_using_postgresql():
5092
        assert two_pubs.loggederror_class.count() == 0
5060 5093
    assert external_formdef.data_class().count() == 0
5061 5094
    assert external_carddef.data_class().count() == 1
5062 5095

  
5063 5096
    # formdata is already deleted: cannot find it again
5064 5097
    perform_items([action], formdata)
5065
    assert LoggedError.count() == 1
5066
    logged_error = LoggedError.select()[0]
5067
    assert logged_error.summary == 'Could not find linked "External Form" object by id %s' % external_formdata.id
5068
    assert logged_error.exception_class == 'KeyError'
5069
    assert logged_error.status_item_id == action.id
5098
    if two_pubs.is_using_postgresql():
5099
        assert two_pubs.loggederror_class.count() == 1
5100
        logged_error = two_pubs.loggederror_class.select()[0]
5101
        assert logged_error.summary == 'Could not find linked "External Form" object by id %s' % external_formdata.id
5102
        assert logged_error.exception_class == 'KeyError'
5103
        assert logged_error.status_item_id == action.id
5070 5104

  
5071 5105
    # try remove an unexisting carddef: do nothing
5072 5106
    unused_carddef = CardDef()
......
5074 5108
    unused_carddef.fields = []
5075 5109
    unused_carddef.workflow = external_wf
5076 5110
    unused_carddef.store()
5077
    LoggedError.wipe()
5111
    if two_pubs.is_using_postgresql():
5112
        two_pubs.loggederror_class.wipe()
5078 5113
    action.slug = 'carddef:%s' % unused_carddef.url_name
5079 5114
    wf.store()
5080 5115
    perform_items([action], formdata)
5081
    assert LoggedError.count() == 0
5116
    if two_pubs.is_using_postgresql():
5117
        assert two_pubs.loggederror_class.count() == 0
5082 5118
    assert external_formdef.data_class().count() == 0
5083 5119
    assert external_carddef.data_class().count() == 1
5084 5120
    # remove the right carddef
5085 5121
    action.slug = 'carddef:%s' % external_carddef.url_name
5086 5122
    wf.store()
5087 5123
    perform_items([action], formdata)
5088
    assert LoggedError.count() == 0
5124
    if two_pubs.is_using_postgresql():
5125
        assert two_pubs.loggederror_class.count() == 0
5089 5126
    assert external_formdef.data_class().count() == 0
5090 5127
    assert external_carddef.data_class().count() == 0
5091 5128

  
5092 5129

  
5093
def test_call_external_workflow_with_data_sourced_object(pub):
5130
def test_call_external_workflow_with_data_sourced_object(two_pubs):
5094 5131
    FormDef.wipe()
5095 5132
    CardDef.wipe()
5096
    LoggedError.wipe()
5133
    if two_pubs.is_using_postgresql():
5134
        two_pubs.loggederror_class.wipe()
5097 5135

  
5098 5136
    carddef_wf = Workflow(name='Carddef Workflow')
5099 5137
    carddef_wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(carddef_wf)
......
5154 5192
    formdef.workflow = wf
5155 5193
    formdef.store()
5156 5194

  
5157
    assert LoggedError.count() == 0
5195
    if two_pubs.is_using_postgresql():
5196
        assert two_pubs.loggederror_class.count() == 0
5158 5197
    assert carddef.data_class().count() == 1
5159 5198

  
5160 5199
    formdata = formdef.data_class()()
......
5164 5203
    formdata.perform_workflow()
5165 5204

  
5166 5205
    perform_items([update_action], formdata)
5167
    assert LoggedError.count() == 0
5206
    if two_pubs.is_using_postgresql():
5207
        assert two_pubs.loggederror_class.count() == 0
5168 5208
    assert carddef.data_class().count() == 1
5169 5209
    data = carddef.data_class().select()[0]
5170 5210
    assert data.data['bo0'] == '1'
......
5174 5214
    assert data.data['bo0'] == '2'
5175 5215

  
5176 5216
    perform_items([delete_action], formdata)
5177
    assert LoggedError.count() == 0
5217
    if two_pubs.is_using_postgresql():
5218
        assert two_pubs.loggederror_class.count() == 0
5178 5219
    assert carddef.data_class().count() == 0
5179 5220

  
5180 5221
    perform_items([delete_action], formdata)
5181
    assert LoggedError.count() == 1
5182
    logged_error = LoggedError.select()[0]
5183
    assert logged_error.summary == 'Could not find linked "Data" object by id %s' % carddata.id
5184
    assert logged_error.exception_class == 'KeyError'
5222
    if two_pubs.is_using_postgresql():
5223
        assert two_pubs.loggederror_class.count() == 1
5224
        logged_error = two_pubs.loggederror_class.select()[0]
5225
        assert logged_error.summary == 'Could not find linked "Data" object by id %s' % carddata.id
5226
        assert logged_error.exception_class == 'KeyError'
5185 5227

  
5186 5228

  
5187 5229
def test_call_external_workflow_with_parent_object(pub):
5188 5230
    FormDef.wipe()
5189 5231
    CardDef.wipe()
5190
    LoggedError.wipe()
5191 5232

  
5192 5233
    # carddef workflow, with global action to increment a counter in its
5193 5234
    # backoffice fields.
......
5274 5315
def test_call_external_workflow_use_caller_variable(pub):
5275 5316
    FormDef.wipe()
5276 5317
    CardDef.wipe()
5277
    LoggedError.wipe()
5278 5318

  
5279 5319
    # carddef workflow, with global action to set a value in a backoffice field
5280 5320
    carddef_wf = Workflow(name='Carddef Workflow')
......
5349 5389
def test_edit_carddata_with_data_sourced_object(pub):
5350 5390
    FormDef.wipe()
5351 5391
    CardDef.wipe()
5352
    LoggedError.wipe()
5353 5392

  
5354 5393
    datasource = {
5355 5394
        'type': 'formula',
......
5462 5501
def test_edit_carddata_with_linked_object(pub):
5463 5502
    FormDef.wipe()
5464 5503
    CardDef.wipe()
5465
    LoggedError.wipe()
5466 5504

  
5467 5505
    carddef = CardDef()
5468 5506
    carddef.name = 'Parent'
tests/test_wscall.py
3 3

  
4 4
from wcs import fields
5 5
from wcs.formdef import FormDef
6
from wcs.logged_errors import LoggedError
7 6
from wcs.qommon.http_request import HTTPRequest
8 7
from wcs.qommon.template import Template
9 8
from wcs.wscalls import NamedWsCall
......
27 26

  
28 27
def test_named_wscall(pub):
29 28
    # create object
29
    NamedWsCall.wipe()
30 30
    wscall = NamedWsCall()
31 31
    wscall.name = 'Hello'
32 32
    wscall.request = {'url': 'http://example.net', 'qs_data': {'a': 'b'}}
......
172 172

  
173 173
@pytest.mark.parametrize('notify_on_errors', [True, False])
174 174
@pytest.mark.parametrize('record_on_errors', [True, False])
175
def test_webservice_on_error(http_requests, pub, emails, notify_on_errors, record_on_errors):
175
def test_webservice_on_error_with_sql(http_requests, emails, notify_on_errors, record_on_errors):
176
    pub = create_temporary_pub(sql_mode=True)
176 177
    pub.cfg['debug'] = {'error_email': 'errors@localhost.invalid'}
177 178
    pub.write_cfg()
178 179

  
179 180
    NamedWsCall.wipe()
180
    LoggedError.wipe()
181
    pub.loggederror_class.wipe()
181 182
    FormDef.wipe()
182 183

  
183 184
    wscall = NamedWsCall()
......
200 201
        resp = get_app(pub).get('/foobar/')
201 202
        assert 'Foo Bar ' in resp.text
202 203
        assert emails.count() == 0
203
        assert LoggedError.count() == 0
204
        assert pub.loggederror_class.count() == 0
204 205

  
205 206
    for url_part in ['400', '400-json', '404', '404-json', '500', 'json-err1', 'json-errheader1']:
206 207
        status_code = 200
......
217 218
        else:
218 219
            assert emails.count() == 0
219 220
        if record_on_errors:
220
            assert LoggedError.count() == 1
221
            LoggedError.wipe()
221
            assert pub.loggederror_class.count() == 1
222
            pub.loggederror_class.wipe()
222 223
        else:
223
            assert LoggedError.count() == 0
224
            assert pub.loggederror_class.count() == 0
tests/utilities.py
4 4
import tempfile
5 5
import random
6 6
import psycopg2
7
import pytest
8 7
import shutil
9 8
import sys
10
import threading
11 9

  
12 10
from wcs import sql, sessions, custom_views
13 11

  
......
20 18
from wcs.qommon import force_str
21 19
import wcs
22 20
import wcs.wsgi
23
from wcs import publisher, compat
24
from wcs.qommon.http_request import HTTPRequest
21
from wcs import compat
25 22
from wcs.users import User
26 23
from wcs.tracking_code import TrackingCode
27 24
import wcs.qommon.emails
......
81 78
        pub.session_class = sql.Session
82 79
        pub.custom_view_class = sql.CustomView
83 80
        pub.snapshot_class = sql.Snapshot
81
        pub.loggederror_class = sql.LoggedError
84 82
        pub.is_using_postgresql = lambda: True
85 83
    else:
86 84
        pub.user_class = User
87 85
        pub.tracking_code_class = TrackingCode
88 86
        pub.session_class = sessions.BasicSession
89 87
        pub.custom_view_class = custom_views.CustomView
90
        pub.snapshot_class = None
91 88
        pub.is_using_postgresql = lambda: False
92 89

  
93 90
    pub.session_manager_class = sessions.StorageSessionManager
......
172 169
        sql.do_session_table()
173 170
        sql.do_custom_views_table()
174 171
        sql.do_snapshots_table()
172
        sql.do_loggederrors_table()
175 173
        sql.do_meta_table()
176 174

  
177 175
        conn.close()
wcs/admin/logged_errors.py
24 24
from wcs.qommon import errors, get_cfg
25 25
from wcs.qommon.misc import localstrftime
26 26

  
27
from wcs.logged_errors import LoggedError
28

  
29 27

  
30 28
class LoggedErrorDirectory(Directory):
31 29
    _q_exports = ['', 'delete', 'ack']
......
107 105
        return redirect('.')
108 106

  
109 107
    def delete(self):
110
        self.error.remove_self()
108
        get_publisher().loggederror_class.remove_object(self.error.id)
111 109
        return redirect('..')
112 110

  
113 111

  
......
117 115
    @classmethod
118 116
    def get_errors(cls, formdef_class=None, formdef_id=None, workflow_id=None):
119 117
        errors = []
118
        if not get_publisher().loggederror_class:
119
            return errors
120 120
        if formdef_id and formdef_class:
121
            errors = [e for e in LoggedError.get_with_indexed_value('formdef_id', formdef_id) if e.formdef_class == formdef_class.__name__]
121
            errors = [
122
                e for e in get_publisher().loggederror_class.get_with_indexed_value('formdef_id', formdef_id)
123
                if e.formdef_class == formdef_class.__name__]
122 124
        elif workflow_id:
123
            errors = LoggedError.get_with_indexed_value('workflow_id', workflow_id)
125
            errors = get_publisher().loggederror_class.get_with_indexed_value('workflow_id', workflow_id)
124 126
        return list(errors)
125 127

  
126 128
    @classmethod
......
168 170

  
169 171
    def _q_lookup(self, component):
170 172
        try:
171
            error = LoggedError.get(component)
173
            error = get_publisher().loggederror_class.get(component)
172 174
        except KeyError:
173 175
            raise errors.TraversalError()
174 176
        get_response().breadcrumb.append(('logged-errors/', _('Logged Errors')))
wcs/conditions.py
56 56
            if self.log_errors:
57 57
                get_logger().warning('failed to evaluate %r (%r)', self, e)
58 58
            if self.record_errors:
59
                from wcs.logged_errors import LoggedError
60 59
                summary = _('Failed to evaluate condition')
61
                LoggedError.record(summary,
62
                                   formdata=self.context.get('formdata'),
63
                                   status_item=self.context.get('status_item'),
64
                                   expression=self.value, expression_type=self.type,
65
                                   exception=e)
60
                get_publisher().record_error(
61
                    summary,
62
                    formdata=self.context.get('formdata'),
63
                    status_item=self.context.get('status_item'),
64
                    expression=self.value, expression_type=self.type,
65
                    exception=e)
66 66
            raise RuntimeError()
67 67

  
68 68
    def evaluate_python(self, local_variables):
wcs/data_sources.py
512 512
        url += param_name + '=' + urllib.quote(param_value)
513 513

  
514 514
        def find_item(items, name, value):
515
            from wcs.logged_errors import LoggedError
516 515
            for item in items:
517 516
                if str(item.get(name)) == str(value):
518 517
                    return item
519 518
            # not found
520
            LoggedError.record(_('Could not find element by id "%s"') % value)
519
            get_publisher().record_error(_('Could not find element by id "%s"') % value)
521 520
            return None
522 521

  
523 522
        request = get_request()
wcs/fields.py
387 387
            except TemplateError:
388 388
                return ('', explicit_lock)
389 389
            except AttributeError as e:
390
                from wcs.logged_errors import LoggedError
391
                LoggedError.record(_('Failed to evaluate prefill on field "%s"') % self.label, formdef=getattr(self, 'formdef', None), exception=e)
390
                get_publisher().record_error(
391
                    _('Failed to evaluate prefill on field "%s"') % self.label,
392
                    formdef=getattr(self, 'formdef', None),
393
                    exception=e)
392 394
                return ('', explicit_lock)
393 395

  
394 396
        elif t == 'user' and user:
wcs/formdata.py
601 601
        if status_id == '_previous':
602 602
            previous_status = self.pop_previous_marked_status()
603 603
            if not previous_status:
604
                from wcs.logged_errors import LoggedError
605 604
                summary = _('Failed to compute previous status')
606
                LoggedError.record(summary, formdata=self)
605
                get_publisher().record_error(summary, formdata=self)
607 606
                return
608 607
            status_id = previous_status.id
609 608
        status = 'wf-%s' % status_id
......
1214 1213
    def iter_target_datas(self, objectdef=None, object_type=None, status_item=None):
1215 1214
        # objectdef, object_type and status_item are provided when called from a workflow action
1216 1215
        from wcs.wf.create_formdata import LinkedFormdataEvolutionPart
1217
        from wcs.logged_errors import LoggedError
1218 1216
        from .carddef import CardDef
1219 1217
        from .formdef import FormDef
1220 1218

  
......
1268 1266
                    yield objectdef.data_class().get(target_id)
1269 1267
                except KeyError as e:
1270 1268
                    # use custom error message depending on target type
1271
                    LoggedError.record(_('Could not find linked "%(object_name)s" object by id %(object_id)s') % {
1272
                        'object_name': objectdef.name, 'object_id': target_id},
1269
                    get_publisher().record_error(
1270
                        _('Could not find linked "%(object_name)s" object by id %(object_id)s') % {
1271
                            'object_name': objectdef.name, 'object_id': target_id},
1273 1272
                        formdata=self, status_item=status_item, exception=e)
1274 1273
            else:
1275 1274
                # inspect page
wcs/logged_errors.py
17 17
import datetime
18 18

  
19 19
from .qommon.misc import simplify
20
from .qommon.xml_storage import XmlStorableObject
21 20
from wcs.carddef import CardDef
22 21
from wcs.formdef import FormDef
23 22
from wcs.workflows import Workflow
24 23

  
25 24

  
26
class LoggedError(XmlStorableObject):
25
class LoggedError(object):
27 26
    _names = 'logged-errors'
28
    xml_root_node = 'error'
29
    _indexes = ['tech_id']
30
    _hashed_indexes = ['formdef_id', 'workflow_id']
31 27

  
28
    id = None
29
    tech_id = None
32 30
    summary = None
33 31
    formdef_class = 'FormDef'
34 32
    formdata_id = None
......
46 44
    latest_occurence_timestamp = None
47 45
    acked = False
48 46

  
49
    # declarations for serialization
50
    XML_NODES = [
51
            ('summary', 'str'), ('traceback', 'str'),
52
            ('exception_class', 'str'), ('exception_message', 'str'),
53
            ('expression', 'str'), ('expression_type', 'str'),
54
            ('formdata_id', 'str'), ('formdef_id', 'str'), ('workflow_id', 'str'),
55
            ('formdef_class', 'str'),
56
            ('status_id', 'str'), ('status_item_id', 'str'),
57
            ('occurences_count', 'int'),
58
            ('first_occurence_timestamp', 'datetime'),
59
            ('latest_occurence_timestamp', 'datetime'),
60
            ('acked', 'bool')]
61

  
62 47
    @classmethod
63 48
    def record(cls, error_summary, plain_error_msg=None, formdata=None,
64 49
               formdef=None, workflow=None, status=None, status_item=None,
......
96 81
            error.status_id = status.id
97 82

  
98 83
        error.first_occurence_timestamp = datetime.datetime.now()
99
        error.id = '%s-%s' % (
100
                error.first_occurence_timestamp.strftime('%Y%m%d-%H%M%S'),
101
                error.tech_id,
102
                )
103
        existing_error = cls.get_on_index(error.tech_id, 'tech_id', ignore_errors=True)
104
        if existing_error:
105
            error = existing_error
84
        error.tech_id = error.build_tech_id()
85
        existing_errors = list(cls.get_with_indexed_value('tech_id', error.tech_id))
86
        if existing_errors:
87
            error = existing_errors[0]
106 88
        error.occurences_count += 1
107 89
        error.latest_occurence_timestamp = datetime.datetime.now()
108 90
        error.store()
......
112 94
    def record_exception(cls, error_summary, plain_error_msg, publisher):
113 95
        try:
114 96
            context = publisher.substitutions.get_context_variables()
115
        except Exception as e:
97
        except Exception:
116 98
            return
117 99
        formdata_id = context.get('form_number_raw')
118 100
        formdef_urlname = context.get('form_slug')
......
125 107
            klass = CardDef
126 108
        formdef = klass.get_by_urlname(formdef_urlname)
127 109
        formdata = formdef.data_class().get(formdata_id, ignore_errors=True)
128
        return cls.record(error_summary, plain_error_msg, formdata=formdata,
129
                   formdef=formdef, workflow=formdef.workflow)
110
        return cls.record(
111
            error_summary, plain_error_msg, formdata=formdata,
112
            formdef=formdef, workflow=formdef.workflow)
130 113

  
131
    @property
132
    def tech_id(self):
114
    def build_tech_id(self):
133 115
        tech_id = ''
134 116
        if self.formdef_id:
135 117
            tech_id += '%s-' % self.formdef_id
wcs/publisher.py
52 52

  
53 53
from .users import User
54 54
from .tracking_code import TrackingCode
55
from .logged_errors import LoggedError
56 55

  
57 56
import pickle
58 57

  
......
153 152
            self.session_class = sql.Session
154 153
            self.custom_view_class = sql.CustomView
155 154
            self.snapshot_class = sql.Snapshot
155
            self.loggederror_class = sql.LoggedError
156 156
            sql.get_connection(new=True)
157 157
        else:
158 158
            self.user_class = User
......
160 160
            self.session_class = sessions.BasicSession
161 161
            self.custom_view_class = custom_views.CustomView
162 162
            self.snapshot_class = None
163
            self.loggederror_class = None
163 164

  
164 165
        self.session_manager_class = sessions.StorageSessionManager
165 166
        self.set_session_manager(self.session_manager_class(session_class=self.session_class))
......
312 313
        sql.do_tracking_code_table()
313 314
        sql.do_custom_views_table()
314 315
        sql.do_snapshots_table()
316
        sql.do_loggederrors_table()
315 317
        sql.do_meta_table()
316 318
        from .formdef import FormDef
317 319
        from .carddef import CardDef
......
340 342

  
341 343
    def log_internal_error(self, error_summary, plain_error_msg, record=False, notify=True):
342 344
        tech_id = None
343
        if record:
344
            logged_exception = LoggedError.record_exception(
345
        if record and self.loggederror_class:
346
            logged_exception = self.loggederror_class.record_exception(
345 347
                    error_summary, plain_error_msg, publisher=self)
346 348
            if logged_exception:
347 349
                tech_id = logged_exception.tech_id
......
357 359
            # this could happen on file descriptor exhaustion
358 360
            pass
359 361

  
362
    def record_error(self, *args, **kwargs):
363
        if self.loggederror_class:
364
            self.loggederror_class.record(*args, **kwargs)
365

  
360 366
    def apply_global_action_timeouts(self):
361 367
        from wcs.workflows import Workflow, WorkflowGlobalActionTimeoutTrigger
362 368
        for workflow in Workflow.select():
wcs/sql.py
45 45
import wcs.carddata
46 46
import wcs.custom_views
47 47
import wcs.formdata
48
import wcs.logged_errors
48 49
import wcs.snapshots
49 50
import wcs.tracking_code
50 51
import wcs.users
......
859 860
    cur.close()
860 861

  
861 862

  
863
def do_loggederrors_table(concurrently=False):
864
    conn, cur = get_connection_and_cursor()
865
    table_name = 'loggederrors'
866

  
867
    cur.execute('''SELECT COUNT(*) FROM information_schema.tables
868
                    WHERE table_schema = 'public'
869
                      AND table_name = %s''', (table_name,))
870
    if cur.fetchone()[0] == 0:
871
        cur.execute('''CREATE TABLE %s (id SERIAL PRIMARY KEY,
872
                                        tech_id VARCHAR UNIQUE,
873
                                        summary VARCHAR,
874
                                        formdef_class VARCHAR,
875
                                        formdata_id VARCHAR,
876
                                        formdef_id VARCHAR,
877
                                        workflow_id VARCHAR,
878
                                        status_id VARCHAR,
879
                                        status_item_id VARCHAR,
880
                                        expression VARCHAR,
881
                                        expression_type VARCHAR,
882
                                        traceback TEXT,
883
                                        exception_class VARCHAR,
884
                                        exception_message VARCHAR,
885
                                        occurences_count INTEGER,
886
                                        first_occurence_timestamp TIMESTAMP WITH TIME ZONE,
887
                                        latest_occurence_timestamp TIMESTAMP WITH TIME ZONE,
888
                                        acked BOOLEAN
889
                                        )''' % table_name)
890
    cur.execute('''SELECT column_name FROM information_schema.columns
891
                    WHERE table_schema = 'public'
892
                      AND table_name = %s''', (table_name,))
893
    existing_fields = set([x[0] for x in cur.fetchall()])
894

  
895
    needed_fields = set([x[0] for x in LoggedError._table_static_fields])
896

  
897
    # delete obsolete fields
898
    for field in (existing_fields - needed_fields):
899
        cur.execute('''ALTER TABLE %s DROP COLUMN %s''' % (table_name, field))
900

  
901
    create_index = 'CREATE INDEX'
902
    if concurrently:
903
        create_index = 'CREATE INDEX CONCURRENTLY'
904

  
905
    # build indexes
906
    existing_indexes = set()
907
    cur.execute('''SELECT indexname
908
                     FROM pg_indexes
909
                    WHERE schemaname = 'public'
910
                      AND tablename = %s''', (table_name,))
911
    existing_indexes = set([x[0] for x in cur.fetchall()])
912

  
913
    for attr in ('formdef_id', 'workflow_id'):
914
        if not table_name + '_' + attr + '_idx' in existing_indexes:
915
            cur.execute('%(create_index)s %(table_name)s_%(attr)s_idx ON %(table_name)s (%(attr)s)' % {
916
                'create_index': create_index,
917
                'table_name': table_name,
918
                'attr': attr})
919

  
920
    conn.commit()
921
    cur.close()
922

  
923

  
862 924
@guard_postgres
863 925
def do_meta_table(conn=None, cur=None, insert_current_sql_level=True):
864 926
    own_conn = False
......
2458 2520
        return cls.get(row[0])
2459 2521

  
2460 2522

  
2523
class LoggedError(SqlMixin, wcs.logged_errors.LoggedError):
2524
    _table_name = 'loggederrors'
2525
    _table_static_fields = [
2526
        ('id', 'serial'),
2527
        ('tech_id', 'varchar'),
2528
        ('summary', 'varchar'),
2529
        ('formdef_class', 'varchar'),
2530
        ('formdata_id', 'varchar'),
2531
        ('formdef_id', 'varchar'),
2532
        ('workflow_id', 'varchar'),
2533
        ('status_id', 'varchar'),
2534
        ('status_item_id', 'varchar'),
2535
        ('expression', 'varchar'),
2536
        ('expression_type', 'varchar'),
2537
        ('traceback', 'text'),
2538
        ('exception_class', 'varchar'),
2539
        ('exception_message', 'varchar'),
2540
        ('occurences_count', 'integer'),
2541
        ('first_occurence_timestamp', 'timestamptz'),
2542
        ('latest_occurence_timestamp', 'timestamptz'),
2543
        ('acked', 'boolean'),
2544
    ]
2545

  
2546
    _numerical_id = False
2547

  
2548
    @guard_postgres
2549
    @invalidate_substitution_cache
2550
    def store(self):
2551
        sql_dict = {x: getattr(self, x) for x, y in self._table_static_fields}
2552

  
2553
        conn, cur = get_connection_and_cursor()
2554
        if not self.id:
2555
            column_names = [x for x in sql_dict.keys() if x != 'id']
2556
            sql_statement = '''INSERT INTO %s (%s)
2557
                               VALUES (%s)
2558
                               RETURNING id''' % (
2559
                                       self._table_name,
2560
                                       ', '.join(column_names),
2561
                                       ', '.join(['%%(%s)s' % x for x in column_names]))
2562
            cur.execute(sql_statement, sql_dict)
2563
            self.id = cur.fetchone()[0]
2564
        else:
2565
            column_names = sql_dict.keys()
2566
            sql_statement = '''UPDATE %s SET %s WHERE id = %%(id)s RETURNING id''' % (
2567
                                       self._table_name,
2568
                                       ', '.join(['%s = %%(%s)s' % (x, x) for x in column_names]))
2569
            cur.execute(sql_statement, sql_dict)
2570
            if cur.fetchone() is None:
2571
                raise AssertionError()
2572

  
2573
        conn.commit()
2574
        cur.close()
2575

  
2576
    @classmethod
2577
    def _row2ob(cls, row, **kwargs):
2578
        o = cls()
2579
        for field, value in zip(cls._table_static_fields, tuple(row)):
2580
            if field[1] in ('varchar', 'text'):
2581
                setattr(o, field[0], str_encode(value))
2582
            else:
2583
                setattr(o, field[0], value)
2584
        return o
2585

  
2586
    @classmethod
2587
    def get_data_fields(cls):
2588
        return []
2589

  
2590
    @classmethod
2591
    @guard_postgres
2592
    def fix_sequences(cls):
2593
        conn, cur = get_connection_and_cursor()
2594

  
2595
        sql_statement = '''select max(id) from %s''' % cls._table_name
2596
        cur.execute(sql_statement)
2597
        max_id = cur.fetchone()[0]
2598
        if max_id is not None:
2599
            sql_statement = '''ALTER SEQUENCE %s_id_seq RESTART %s''' % (
2600
                    cls._table_name, max_id+1)
2601
            cur.execute(sql_statement)
2602

  
2603
        conn.commit()
2604
        cur.close()
2605

  
2606

  
2461 2607
class classproperty(object):
2462 2608
    def __init__(self, f):
2463 2609
        self.f = f
......
2716 2862
# latest migration, number + description (description is not used
2717 2863
# programmaticaly but will make sure git conflicts if two migrations are
2718 2864
# separately added with the same number)
2719
SQL_LEVEL = (46, 'add index on formdata(status) - fix')
2865
SQL_LEVEL = (47, 'use SQL to store LoggedError')
2720 2866

  
2721 2867

  
2722 2868
def migrate_global_views(conn, cur):
......
2871 3017
    if sql_level < 42:
2872 3018
        # 42: create snapshots table
2873 3019
        do_snapshots_table()
3020
    if sql_level < 47:
3021
        # 47: store LoggedErrors in SQL
3022
        do_loggederrors_table()
2874 3023

  
2875 3024
    cur.execute('''UPDATE wcs_meta SET value = %s WHERE key = %s''', (
2876 3025
        str(SQL_LEVEL[0]), 'sql_level'))
wcs/variables.py
24 24

  
25 25
from pyproj import Geod
26 26

  
27
from .logged_errors import LoggedError
28 27
from .qommon import misc, force_str, _
29 28
from .qommon.evalutils import make_datetime
30 29
from .qommon.templatetags.qommon import parse_datetime
......
169 168
                from wcs import sql
170 169
                criteria = Equal(sql.get_field_id(field), value)
171 170
                return self._clone(self._criterias + [criteria])
172
        LoggedError.record(_('Invalid filter "%s"') % self.pending_attr, formdata=self._formdata)
171
        get_publisher().record_error(_('Invalid filter "%s"') % self.pending_attr, formdata=self._formdata)
173 172
        return self.none()
174 173

  
175 174
    def __getattr__(self, attribute):
wcs/wf/backoffice_fields.py
15 15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
import xml.etree.ElementTree as ET
18

  
19
from quixote import get_publisher
18 20
from quixote.html import htmltext
19 21

  
20 22
from ..qommon import _, N_
......
133 135
                try:
134 136
                    new_value = formdef_field.convert_value_from_anything(new_value)
135 137
                except ValueError as e:
136
                    from wcs.logged_errors import LoggedError
137 138
                    summary = _('Failed to convert %(class)s value to %(kind)s field (%(id)s)') % {
138 139
                                    'class': type(new_value),
139 140
                                    'kind': _(getattr(formdef_field, 'description', 'unknown')),
140 141
                                    'id': field['field_id'],
141 142
                                }
142 143
                    expression_dict = self.get_expression(field['value'])
143
                    LoggedError.record(summary, formdata=formdata, status_item=self,
144
                                       expression=expression_dict['value'],
145
                                       expression_type=expression_dict['type'],
146
                                       exception=e)
144
                    get_publisher().record_error(
145
                        summary, formdata=formdata, status_item=self,
146
                        expression=expression_dict['value'],
147
                        expression_type=expression_dict['type'],
148
                        exception=e)
147 149
                    continue
148 150

  
149 151
            formdata.data['%s' % field['field_id']] = new_value
wcs/wf/create_formdata.py
27 27
                             SingleSelectWidget, ComputedExpressionWidget,
28 28
                             CheckboxWidget, VarnameWidget, HtmlWidget)
29 29

  
30
from wcs.logged_errors import LoggedError
31 30
from wcs.workflows import WorkflowStatusItem, register_item_class
32 31
from wcs.formdef import FormDef
33 32

  
......
391 390
                        field=dest_field,
392 391
                        value=src.data.get(field.id))
393 392
                except Exception as e:
394
                    LoggedError.record(_('Could not copy field by varname for "%s"') % field.varname,
395
                                       formdata=src, status_item=self, exception=e)
393
                    get_publisher().record_error(
394
                        _('Could not copy field by varname for "%s"') % field.varname,
395
                        formdata=src, status_item=self, exception=e)
396 396

  
397 397
        # field.id can be serialized to xml, so we must always convert them to
398 398
        # str when matching
......
418 418
                    value=value)
419 419
            except Exception as e:
420 420
                expression = self.get_expression(mapping.expression)
421
                LoggedError.record(_('Could not assign value to field "%s"') % dest_field.label,
422
                                   formdata=src, status_item=self,
423
                                   expression=expression['value'], expression_type=expression['type'],
424
                                   exception=e)
421
                get_publisher().record_error(
422
                    _('Could not assign value to field "%s"') % dest_field.label,
423
                    formdata=src, status_item=self,
424
                    expression=expression['value'], expression_type=expression['type'],
425
                    exception=e)
425 426

  
426 427
        if missing_fields:
427 428
            summary = _('Missing field %r') % missing_fields
428
            LoggedError.record(summary, formdata=src, status_item=self)
429
            get_publisher().record_error(summary, formdata=src, status_item=self)
429 430

  
430 431
    def _set_value(self, formdata, field, value):
431 432
        if field.convert_value_from_anything:
wcs/wf/dispatch.py
188 188
        return htmltext('<ul class="rules">%s</ul>') % htmltext('').join(result)
189 189

  
190 190
    def perform(self, formdata):
191
        from wcs.logged_errors import LoggedError
192 191
        if not formdata.workflow_roles:
193 192
            formdata.workflow_roles = {}
194 193

  
......
199 198
                return
200 199
            new_role_id = self.get_computed_role_id(self.role_id)
201 200
            if not new_role_id:
202
                LoggedError.record(_('error in dispatch, missing role (%s)') % self.role_id, formdata=formdata)
201
                get_publisher().record_error(_('error in dispatch, missing role (%s)') % self.role_id, formdata=formdata)
203 202
        elif self.dispatch_type == 'automatic':
204 203
            if not (self.role_key and self.variable and self.rules):
205 204
                return
......
226 225

  
227 226
        if new_role_id:
228 227
            if not Role.has_key(new_role_id):
229
                LoggedError.record(_('error in dispatch, missing role (%s)') % new_role_id, formdata=formdata)
228
                get_publisher().record_error(_('error in dispatch, missing role (%s)') % new_role_id, formdata=formdata)
230 229
            else:
231 230
                formdata.workflow_roles[self.role_key] = str(new_role_id)
232 231
                formdata.store()
wcs/wf/external_workflow.py
19 19
from wcs.qommon import _
20 20
from wcs.qommon.form import SingleSelectWidget
21 21

  
22
from wcs.logged_errors import LoggedError
23 22
from wcs.workflows import WorkflowStatusItem, perform_items, register_item_class
24 23
from wcs.workflows import WorkflowGlobalActionWebserviceTrigger, Workflow
25 24
from wcs.carddef import CardDef
......
126 125

  
127 126
        trigger = self.get_trigger(objectdef.workflow)
128 127
        if not trigger:
129
            LoggedError.record(_('No trigger with id "%s" found in workflow') % self.trigger_id,
130
                    formdata=formdata, status_item=self)
128
            get_publisher().record_error(
129
                _('No trigger with id "%s" found in workflow') % self.trigger_id,
130
                formdata=formdata, status_item=self)
131 131
            return
132 132

  
133 133
        class CallerSource:
wcs/wf/wscall.py
447 447
            try:
448 448
                target = self.parent.parent.get_status(value)
449 449
            except KeyError:
450
                from wcs.logged_errors import LoggedError
451 450
                message = _('reference to invalid status in workflow %(workflow)s, status %(status)s, item %(item)s') % {
452 451
                    'workflow': self.parent.parent.name,
453 452
                    'status': self.parent.name,
454 453
                    'item': self.description,
455 454
                }
456
                LoggedError.record(message, workflow=self.parent.parent)
455
                get_publisher().record_error(message, workflow=self.parent.parent)
457 456
                continue
458 457
            targets.append(target)
459 458
        return targets
wcs/workflows.py
1982 1982
        vars.update(context or {})
1983 1983

  
1984 1984
        def log_exception(exception):
1985
            from wcs.logged_errors import LoggedError
1986 1985
            if expression['type'] == 'template':
1987 1986
                summary = _('Failed to compute template')
1988 1987
            else:
1989 1988
                summary = _('Failed to compute Python expression')
1990
            LoggedError.record(summary, formdata=formdata, status_item=status_item,
1991
                               expression=expression['value'],
1992
                               expression_type=expression['type'],
1993
                               exception=exception)
1989
            get_publisher().record_error(
1990
                summary, formdata=formdata, status_item=status_item,
1991
                expression=expression['value'],
1992
                expression_type=expression['type'],
1993
                exception=exception)
1994 1994

  
1995 1995
        if expression['type'] == 'template':
1996 1996
            try:
......
2043 2043

  
2044 2044
        targets = [x for x in self.parent.parent.possible_status if x.id == self.status]
2045 2045
        if not targets and formdata:  # do not log in presentation context: formdata is needed
2046
            from wcs.logged_errors import LoggedError
2047 2046
            message = _('reference to invalid status %(target)s in status %(status)s, '
2048 2047
                        'action %(status_item)s') % {
2049 2048
                            'target': self.status,
2050 2049
                            'status': self.parent.name,
2051 2050
                            'status_item': _(self.description)
2052 2051
                        }
2053
            LoggedError.record(message, formdata=formdata, status_item=self)
2052
            get_publisher().record_error(message, formdata=formdata, status_item=self)
2054 2053

  
2055 2054
        return targets
2056 2055

  
......
2677 2676
                subject = mail_template.subject
2678 2677
                extra_attachments = mail_template.attachments
2679 2678
            else:
2680
                from wcs.logged_errors import LoggedError
2681 2679
                message = _('reference to invalid mail template %(mail_template)s in status %(status)s') % {
2682 2680
                            'status': self.parent.name,
2683 2681
                            'mail_template': self.mail_template,
2684 2682
                            }
2685
                LoggedError.record(message, formdata=formdata, status_item=self)
2683
                get_publisher().record_error(message, formdata=formdata, status_item=self)
2686 2684
                return
2687 2685

  
2688 2686
        try:
2689
-