Projet

Général

Profil

0002-errors-list-all-errors-on-one-page-48926.patch

Lauréline Guérin, 15 décembre 2020 09:17

Télécharger (17,8 ko)

Voir les différences:

Subject: [PATCH 2/3] errors: list all errors on one page (#48926)

 tests/admin_pages/test_logged_errors.py       | 243 ++++++++++++++++++
 wcs/admin/logged_errors.py                    |  69 ++++-
 wcs/backoffice/studio.py                      |  32 ++-
 wcs/qommon/backoffice/listing.py              |   5 +-
 wcs/sql.py                                    |  12 +-
 .../wcs/backoffice/logged-errors.html         |   1 +
 6 files changed, 340 insertions(+), 22 deletions(-)
 create mode 100644 tests/admin_pages/test_logged_errors.py
tests/admin_pages/test_logged_errors.py
1
# -*- coding: utf-8 -*-
2

  
3
import datetime
4

  
5
import pytest
6

  
7
from wcs.carddef import CardDef
8
from wcs.formdef import FormDef
9
from wcs.qommon.http_request import HTTPRequest
10
from wcs.workflows import Workflow
11

  
12
from utilities import get_app, login, create_temporary_pub, clean_temporary_pub
13
from .test_all import create_superuser
14

  
15

  
16
@pytest.fixture
17
def pub(request):
18
    pub = create_temporary_pub(sql_mode=True)
19
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
20
    pub.set_app_dir(req)
21
    pub.cfg['identification'] = {'methods': ['password']}
22
    pub.cfg['language'] = {'language': 'en'}
23
    pub.write_cfg()
24
    return pub
25

  
26

  
27
def teardown_module(module):
28
    clean_temporary_pub()
29

  
30

  
31
def test_studio_home(pub):
32
    create_superuser(pub)
33
    app = login(get_app(pub))
34
    resp = app.get('/backoffice/studio/')
35
    assert 'logged-errors/' in resp.text
36

  
37

  
38
def test_listing_paginations(pub):
39
    pub.loggederror_class.wipe()
40

  
41
    FormDef.wipe()
42
    CardDef.wipe()
43
    Workflow.wipe()
44

  
45
    formdef = FormDef()
46
    formdef.name = 'foo'
47
    formdef.store()
48
    formdef2 = FormDef()
49
    formdef2.name = 'foo 2'
50
    formdef2.store()
51
    carddef = CardDef()
52
    carddef.name = 'bar'
53
    carddef.store()
54
    carddef2 = CardDef()
55
    carddef2.name = 'bar 2'
56
    carddef2.store()
57
    workflow = Workflow()
58
    workflow.name = 'blah'
59
    workflow.store()
60
    workflow2 = Workflow()
61
    workflow2.name = 'blah 2'
62
    workflow2.store()
63

  
64
    # FormDef errors
65
    for i in range(0, 21):
66
        error = pub.loggederror_class()
67
        error.summary = 'FormDef Workflow Logged Error n°%s' % i
68
        error.formdef_class = 'FormDef'
69
        error.formdef_id = formdef.id
70
        error.workflow_id = workflow.id
71
        error.first_occurence_timestamp = datetime.datetime.now()
72
        error.store()
73
    error = pub.loggederror_class()
74
    error.summary = 'FormDef 2 Workflow 2 Logged Error n°%s' % i
75
    error.formdef_class = 'FormDef'
76
    error.formdef_id = formdef2.id
77
    error.workflow_id = workflow2.id
78
    error.first_occurence_timestamp = datetime.datetime.now()
79
    error.store()
80

  
81
    # CardDef errors
82
    for i in range(0, 21):
83
        error = pub.loggederror_class()
84
        error.summary = 'CardDef Workflow Logged Error n°%s' % i
85
        error.formdef_class = 'CardDef'
86
        error.formdef_id = carddef.id
87
        error.workflow_id = workflow.id
88
        error.first_occurence_timestamp = datetime.datetime.now()
89
        error.store()
90
    error = pub.loggederror_class()
91
    error.summary = 'CardDef 2 Workflow 2 Logged Error n°%s' % i
92
    error.formdef_class = 'CardDef'
93
    error.formdef_id = carddef2.id
94
    error.workflow_id = workflow2.id
95
    error.first_occurence_timestamp = datetime.datetime.now()
96
    error.store()
97

  
98
    # workflow-only errors
99
    for i in range(0, 21):
100
        error = pub.loggederror_class()
101
        error.summary = 'Workflow Logged Error n°%s' % i
102
        error.workflow_id = workflow.id
103
        error.first_occurence_timestamp = datetime.datetime.now()
104
        error.store()
105
    error = pub.loggederror_class()
106
    error.summary = 'Workflow 2 Logged Error n°%s' % i
107
    error.workflow_id = workflow2.id
108
    error.first_occurence_timestamp = datetime.datetime.now()
109
    error.store()
110

  
111
    create_superuser(pub)
112
    app = login(get_app(pub))
113

  
114
    # all errors
115

  
116
    # default pagination
117
    resp = app.get('/backoffice/studio/logged-errors/')
118
    assert '1-20/66' in resp.text
119
    assert resp.text.count('Logged Error n°') == 20
120
    resp = resp.click(href=r'\?offset=60')
121
    assert '61-66/66' in resp.text
122
    assert resp.text.count('Logged Error n°') == 6
123

  
124
    # change pagination
125
    resp = app.get('/backoffice/studio/logged-errors/?offset=0&limit=50')
126
    assert '1-50/66' in resp.text
127
    assert resp.text.count('Logged Error n°') == 50
128
    resp = resp.click('<!--Next Page-->')
129
    assert '51-66/66' in resp.text
130
    assert resp.text.count('Logged Error n°') == 16
131

  
132
    # formdef errors
133
    resp = app.get('/backoffice/forms/%s/logged-errors/' % formdef.id)
134
    assert '1-20/21' in resp.text
135
    assert resp.text.count('FormDef Workflow Logged Error n°') == 20
136
    resp = resp.click('<!--Next Page-->')
137
    assert '21-21/21' in resp.text
138
    assert resp.text.count('FormDef Workflow Logged Error n°') == 1
139

  
140
    # carddef errors
141
    resp = app.get('/backoffice/cards/%s/logged-errors/' % carddef.id)
142
    assert '1-20/21' in resp.text
143
    assert resp.text.count('CardDef Workflow Logged Error n°') == 20
144
    resp = resp.click('<!--Next Page-->')
145
    assert '21-21/21' in resp.text
146
    assert resp.text.count('CardDef Workflow Logged Error n°') == 1
147

  
148
    # workflows errors
149
    resp = app.get('/backoffice/workflows/%s/logged-errors/' % workflow.id)
150
    assert '1-20/63' in resp.text
151
    assert resp.text.count('Workflow Logged Error n°') == 20
152
    resp = resp.click(href=r'\?offset=60')
153
    assert '61-63/63' in resp.text
154
    assert resp.text.count('Workflow Logged Error n°') == 3
155

  
156

  
157
def test_backoffice_access(pub):
158
    pub.loggederror_class.wipe()
159

  
160
    FormDef.wipe()
161
    CardDef.wipe()
162
    Workflow.wipe()
163

  
164
    formdef = FormDef()
165
    formdef.name = 'foo'
166
    formdef.store()
167
    carddef = CardDef()
168
    carddef.name = 'bar'
169
    carddef.store()
170
    workflow = Workflow()
171
    workflow.name = 'blah'
172
    workflow.store()
173

  
174
    # FormDef error
175
    error1 = pub.loggederror_class()
176
    error1.summary = 'LoggedError'
177
    error1.formdef_class = 'FormDef'
178
    error1.formdef_id = formdef.id
179
    error1.workflow_id = workflow.id
180
    error1.first_occurence_timestamp = datetime.datetime.now()
181
    error1.store()
182

  
183
    # CardDef error
184
    error2 = pub.loggederror_class()
185
    error2.summary = 'LoggedError'
186
    error2.formdef_class = 'CardDef'
187
    error2.formdef_id = carddef.id
188
    error2.workflow_id = workflow.id
189
    error2.first_occurence_timestamp = datetime.datetime.now()
190
    error2.store()
191

  
192
    # workflow-only error
193
    error3 = pub.loggederror_class()
194
    error3.summary = 'LoggedError'
195
    error3.workflow_id = workflow.id
196
    error3.first_occurence_timestamp = datetime.datetime.now()
197
    error3.store()
198

  
199
    create_superuser(pub)
200
    app = login(get_app(pub))
201

  
202
    # check section link are not displayed if user has no access right
203

  
204
    # formdefs are not accessible to current user
205
    pub.cfg['admin-permissions'] = {'forms': ['X']}
206
    pub.write_cfg()
207
    resp = app.get('/backoffice/studio/logged-errors/')
208
    assert resp.text.count('LoggedError') == 2
209
    assert '<a href="%s/">' % error1.id not in resp.text
210
    assert '<a href="%s/">' % error2.id in resp.text
211
    assert '<a href="%s/">' % error3.id in resp.text
212

  
213
    # carddefs are not accessible to current user
214
    pub.cfg['admin-permissions'] = {'cards': ['X']}
215
    pub.write_cfg()
216
    resp = app.get('/backoffice/studio/logged-errors/')
217
    assert resp.text.count('LoggedError') == 2
218
    assert '<a href="%s/">' % error1.id in resp.text
219
    assert '<a href="%s/">' % error2.id not in resp.text
220
    assert '<a href="%s/">' % error3.id in resp.text
221

  
222
    # workflows are not accessible to current user
223
    pub.cfg['admin-permissions'] = {'workflows': ['X']}
224
    pub.write_cfg()
225
    resp = app.get('/backoffice/studio/logged-errors/')
226
    assert resp.text.count('LoggedError') == 2
227
    assert '<a href="%s/">' % error1.id in resp.text
228
    assert '<a href="%s/">' % error2.id in resp.text
229
    assert '<a href="%s/">' % error3.id not in resp.text
230

  
231
    # mix formdefs & workflows
232
    pub.cfg['admin-permissions'] = {'forms': ['X'], 'workflows': ['X']}
233
    pub.write_cfg()
234
    resp = app.get('/backoffice/studio/logged-errors/')
235
    assert resp.text.count('LoggedError') == 1
236
    assert '<a href="%s/">' % error1.id not in resp.text
237
    assert '<a href="%s/">' % error2.id in resp.text
238
    assert '<a href="%s/">' % error3.id not in resp.text
239

  
240
    # mix all
241
    pub.cfg['admin-permissions'] = {'forms': ['X'], 'cards': ['X'], 'workflows': ['X']}
242
    pub.write_cfg()
243
    resp = app.get('/backoffice/studio/logged-errors/', status=403)
wcs/admin/logged_errors.py
17 17
import re
18 18

  
19 19
from django.utils.text import Truncator
20
from quixote import get_response, get_publisher, redirect
20
from quixote import get_response, get_publisher, redirect, get_request
21 21
from quixote.directory import Directory
22 22
from quixote.html import TemplateIO, htmltext
23 23
from wcs.qommon import _, ngettext, N_, template
24
from wcs.qommon import errors, get_cfg
25
from wcs.qommon.misc import localstrftime
24
from wcs.qommon import errors
25
from wcs.qommon import misc
26
from wcs.qommon.backoffice.listing import pagination_links
27
from wcs.qommon.storage import Or, Equal, NotEqual, Null, NotNull
26 28

  
27 29

  
28 30
class LoggedErrorDirectory(Directory):
......
113 115
    _q_exports = ['']
114 116

  
115 117
    @classmethod
116
    def get_errors(cls, formdef_class=None, formdef_id=None, workflow_id=None):
118
    def get_errors(cls, offset, limit, formdef_class=None, formdef_id=None, workflow_id=None, with_total=False):
117 119
        errors = []
118 120
        if not get_publisher().loggederror_class:
119
            return errors
121
            return errors, 0
122

  
123
        select_kwargs = {
124
            'order_by': '-first_occurence_timestamp',
125
            'limit': limit,
126
            'offset': offset,
127
        }
128
        clauses = []
129

  
120 130
        if formdef_id and formdef_class:
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__]
131
            clauses = [Equal('formdef_id', formdef_id), Equal('formdef_class', formdef_class.__name__)]
124 132
        elif workflow_id:
125
            errors = get_publisher().loggederror_class.get_with_indexed_value('workflow_id', workflow_id)
126
        return list(errors)
133
            clauses = [Equal('workflow_id', workflow_id)]
134
        else:
135
            # check permissions, exclude errors related to not accessible items
136
            clauses = []
137
            backoffice_root = get_publisher().get_backoffice_root()
138
            if not backoffice_root.is_accessible('forms'):
139
                clauses.append(Or([NotEqual('formdef_class', 'FormDef'), Null('formdef_class')]))
140
            if not backoffice_root.is_accessible('cards'):
141
                clauses.append(Or([NotEqual('formdef_class', 'CardDef'), Null('formdef_class')]))
142
            if not backoffice_root.is_accessible('workflows'):
143
                # exclude workflow-only errors
144
                clauses.append(NotNull('formdef_class'))
145

  
146
        errors = get_publisher().loggederror_class.select(clause=clauses, **select_kwargs)
147

  
148
        count = 0
149
        if with_total:
150
            count = get_publisher().loggederror_class.count(clauses)
151

  
152
        return list(errors), count
127 153

  
128 154
    @classmethod
129 155
    def errors_block(cls, formdef_class=None, formdef_id=None, workflow_id=None):
130
        errors = cls.get_errors(formdef_class=formdef_class, formdef_id=formdef_id, workflow_id=workflow_id)
156
        # select 3 + 1 last errors
157
        errors = cls.get_errors(offset=0, limit=4, formdef_class=formdef_class, formdef_id=formdef_id, workflow_id=workflow_id)[0]
131 158
        if not errors:
132 159
            return ''
133
        errors.sort(key=lambda x: x.id, reverse=True)
134 160

  
135 161
        r = TemplateIO(html=True)
136 162
        r += htmltext('<div class="bo-block logged-errors">')
......
160 186
        self.workflow_id = workflow_id
161 187

  
162 188
    def _q_index(self):
189
        backoffice_root = get_publisher().get_backoffice_root()
190
        if not (backoffice_root.is_accessible('forms') or
191
                backoffice_root.is_accessible('cards') or
192
                backoffice_root.is_accessible('workflows')):
193
            raise errors.AccessForbiddenError()
194

  
163 195
        get_response().breadcrumb.append(('logged-errors/', _('Logged Errors')))
164 196
        self.parent_dir.html_top(_('Logged Errors'))
197
        limit = misc.get_int_or_400(
198
            get_request().form.get('limit', get_publisher().get_site_option('default-page-size')) or 20)
199
        offset = misc.get_int_or_400(get_request().form.get('offset', 0))
200
        logged_errors, total_count = self.get_errors(
201
            offset=offset, limit=limit,
202
            formdef_class=self.formdef_class, formdef_id=self.formdef_id, workflow_id=self.workflow_id,
203
            with_total=True)
204
        links = ''
205
        if get_publisher().is_using_postgresql():
206
            links = pagination_links(offset, limit, total_count, load_js=False)
165 207
        return template.QommonTemplateResponse(
166 208
                templates=['wcs/backoffice/logged-errors.html'],
167 209
                context={
168
                    'errors': self.get_errors(formdef_class=self.formdef_class, formdef_id=self.formdef_id, workflow_id=self.workflow_id),
210
                    'errors': logged_errors,
211
                    'pagination_links': links,
169 212
                })
170 213

  
171 214
    def _q_lookup(self, component):
wcs/backoffice/studio.py
16 16

  
17 17
from quixote import get_publisher
18 18
from quixote.directory import Directory
19
from ..qommon import _
20
from ..qommon.backoffice.menu import html_top
21
from ..qommon import template
19
from quixote.html import TemplateIO, htmltext
20

  
21
from wcs.admin.logged_errors import LoggedErrorsDirectory
22
from wcs.qommon import _
23
from wcs.qommon import template
24
from wcs.qommon.backoffice.menu import html_top
25
from wcs.qommon.form import get_response
22 26

  
23 27

  
24 28
class StudioDirectory(Directory):
25
    _q_exports = ['']
29
    _q_exports = ['', ('logged-errors', 'logged_errors_dir')]
30

  
31
    def __init__(self):
32
        self.logged_errors_dir = LoggedErrorsDirectory(parent_dir=self)
33

  
34
    def get_sidebar(self):
35
        r = TemplateIO(html=True)
36
        r += htmltext('<ul id="sidebar-actions">')
37
        r += htmltext('<li><a href="logged-errors/">%s</a></li>') % _('Logged Errors')
38
        r += htmltext('</ul>')
39
        return r.getvalue()
40

  
41
    def html_top(self, title):
42
        return html_top('studio', title)
43

  
44
    def _q_traverse(self, path):
45
        get_response().breadcrumb.append(('studio/', _('Studio')))
46
        return super()._q_traverse(path)
26 47

  
27 48
    def _q_index(self):
28
        html_top('studio', _('Studio'))
49
        self.html_top(_('Studio'))
50
        get_response().filter['sidebar'] = self.get_sidebar()
29 51
        return template.QommonTemplateResponse(
30 52
                templates=['wcs/backoffice/studio.html'],
31 53
                context={})
wcs/qommon/backoffice/listing.py
21 21
from .. import _
22 22

  
23 23

  
24
def pagination_links(offset, limit, total_count):
24
def pagination_links(offset, limit, total_count, load_js=True):
25 25
    limit = limit or 10  # make sure a limit is set
26
    get_response().add_javascript(['wcs.listing.js'])
26
    if load_js:
27
        get_response().add_javascript(['wcs.listing.js'])
27 28
    # pagination
28 29
    r = TemplateIO(html=True)
29 30
    r += htmltext('<div id="page-links">')
wcs/sql.py
1185 1185

  
1186 1186
    @classmethod
1187 1187
    @guard_postgres
1188
    def get_with_indexed_value(cls, index, value, ignore_errors = False):
1188
    def get_with_indexed_value(cls, index, value, ignore_errors=False, order_by=None, limit=None, offset=None):
1189 1189
        conn, cur = get_connection_and_cursor()
1190 1190
        sql_statement = '''SELECT %s
1191 1191
                             FROM %s
......
1194 1194
                                              + cls.get_data_fields()),
1195 1195
                                    cls._table_name,
1196 1196
                                    index)
1197
        cur.execute(sql_statement, {'value': str(value)})
1197
        sql_statement += cls.get_order_by_clause(order_by)
1198
        parameters = {'value': str(value)}
1199
        if limit:
1200
            sql_statement += ' LIMIT %(limit)s'
1201
            parameters['limit'] = limit
1202
        if offset:
1203
            sql_statement += ' OFFSET %(offset)s'
1204
            parameters['offset'] = offset
1205
        cur.execute(sql_statement, parameters)
1198 1206
        try:
1199 1207
            while True:
1200 1208
                row = cur.fetchone()
wcs/templates/wcs/backoffice/logged-errors.html
13 13
          </a><span class="badge">{{ error.occurences_count }}</span></li>
14 14
  {% endfor %}
15 15
</ul>
16
{{ pagination_links|safe }}
16 17
{% endblock %}
17
-