0002-errors-list-all-errors-on-one-page-48926.patch
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 |
- |