0002-studio-admin-can-see-all-changes-62953.patch
tests/admin_pages/test_studio.py | ||
---|---|---|
218 | 218 |
pub.cfg['admin-permissions'].update({'settings': ['x']}) |
219 | 219 |
pub.write_cfg() |
220 | 220 | |
221 |
app = login(get_app(pub)) |
|
222 | 221 |
resp = app.get('/backoffice/studio/') |
223 | 222 |
# no access to settings |
224 | 223 |
for i in range(6): |
... | ... | |
256 | 255 |
pub.cfg['admin-permissions'].update({'settings': ['x'], 'forms': ['x']}) |
257 | 256 |
pub.write_cfg() |
258 | 257 | |
259 |
app = login(get_app(pub)) |
|
260 | 258 |
resp = app.get('/backoffice/studio/') |
261 | 259 |
# no access to settings or forms |
262 | 260 |
for i in range(6): |
... | ... | |
292 | 290 |
pub.cfg['admin-permissions'].update({'settings': ['x'], 'forms': ['x'], 'workflows': ['x']}) |
293 | 291 |
pub.write_cfg() |
294 | 292 | |
295 |
app = login(get_app(pub)) |
|
296 | 293 |
resp = app.get('/backoffice/studio/') |
297 | 294 |
# no access to settings, forms or workflows |
298 | 295 |
for i in range(6): |
... | ... | |
317 | 314 |
assert 'backoffice/cards/%s/' % objects[CardDef.xml_root_node][i].id in resp |
318 | 315 | |
319 | 316 |
objects[CardDef.xml_root_node][5].remove_self() |
320 |
app = login(get_app(pub)) |
|
321 | 317 |
resp = app.get('/backoffice/studio/') |
322 | 318 |
# too old |
323 | 319 |
assert 'backoffice/cards/%s/' % objects[CardDef.xml_root_node][0].id not in resp |
... | ... | |
327 | 323 |
# deleted |
328 | 324 |
assert 'backoffice/cards/%s/' % objects[CardDef.xml_root_node][5].id not in resp |
329 | 325 | |
326 |
# all changes page: adin user can see all changes (dependig on permissions) |
|
327 |
resp = resp.click(href='all-changes/') |
|
328 |
assert '(1-6/6)' in resp |
|
329 |
# he can also see changes from other users |
|
330 |
for snapshot in pub.snapshot_class.select(): |
|
331 |
snapshot.user_id = other_user.id |
|
332 |
snapshot.store() |
|
333 | ||
334 |
pub.cfg['admin-permissions'] = {} |
|
335 |
pub.write_cfg() |
|
336 |
resp = app.get('/backoffice/studio/all-changes/') |
|
337 |
assert '(1-20/42)' in resp |
|
338 |
resp = resp.click('<!--Next Page-->') |
|
339 |
assert '21-40/42' in resp.text |
|
340 |
resp = resp.click('<!--Next Page-->') |
|
341 |
assert '41-42/42' in resp.text |
|
342 | ||
343 |
user.is_admin = False |
|
344 |
user.store() |
|
345 |
app.get('/backoffice/studio/all-changes/', status=403) |
|
346 | ||
330 | 347 | |
331 | 348 |
def test_studio_workflows(pub): |
332 | 349 |
create_superuser(pub) |
wcs/backoffice/studio.py | ||
---|---|---|
24 | 24 |
from wcs.data_sources import NamedDataSource |
25 | 25 |
from wcs.formdef import FormDef |
26 | 26 |
from wcs.mail_templates import MailTemplate |
27 |
from wcs.qommon import _, pgettext, template |
|
27 |
from wcs.qommon import _, misc, pgettext, template |
|
28 |
from wcs.qommon.backoffice.listing import pagination_links |
|
28 | 29 |
from wcs.qommon.backoffice.menu import html_top |
29 | 30 |
from wcs.qommon.form import get_response |
30 | 31 |
from wcs.workflows import Workflow |
31 | 32 |
from wcs.wscalls import NamedWsCall |
32 | 33 | |
33 | 34 | |
35 |
class ChangesDirectory(Directory): |
|
36 |
_q_exports = [''] |
|
37 | ||
38 |
def _q_index(self): |
|
39 |
get_response().breadcrumb.append(('all-changes/', pgettext('studio', 'All changes'))) |
|
40 |
html_top(pgettext('studio', 'All Changes')) |
|
41 |
limit = misc.get_int_or_400( |
|
42 |
get_request().form.get('limit', get_publisher().get_site_option('default-page-size')) or 20 |
|
43 |
) |
|
44 |
offset = misc.get_int_or_400(get_request().form.get('offset', 0)) |
|
45 | ||
46 |
backoffice_root = get_publisher().get_backoffice_root() |
|
47 |
object_types = [] |
|
48 |
if backoffice_root.is_accessible('workflows'): |
|
49 |
object_types += [Workflow, MailTemplate] |
|
50 |
if backoffice_root.is_accessible('forms'): |
|
51 |
object_types += [NamedDataSource, BlockDef, FormDef] |
|
52 |
if backoffice_root.is_accessible('workflows'): |
|
53 |
object_types += [NamedDataSource] |
|
54 |
if backoffice_root.is_accessible('settings'): |
|
55 |
object_types += [NamedDataSource, NamedWsCall] |
|
56 |
if backoffice_root.is_accessible('cards'): |
|
57 |
object_types += [CardDef] |
|
58 |
object_types = [ot.xml_root_node for ot in object_types] |
|
59 | ||
60 |
objects = [] |
|
61 |
links = '' |
|
62 |
if get_publisher().snapshot_class: |
|
63 |
objects = get_publisher().snapshot_class.get_recent_changes( |
|
64 |
object_types=object_types, limit=limit, offset=offset |
|
65 |
) |
|
66 |
total_count = get_publisher().snapshot_class.count_recent_changes(object_types=object_types) |
|
67 |
links = pagination_links(offset, limit, total_count, load_js=False) |
|
68 | ||
69 |
return template.QommonTemplateResponse( |
|
70 |
templates=['wcs/backoffice/changes.html'], |
|
71 |
context={ |
|
72 |
'objects': objects, |
|
73 |
'pagination_links': links, |
|
74 |
}, |
|
75 |
) |
|
76 | ||
77 |
def is_accessible(self, user): |
|
78 |
return user.is_admin |
|
79 | ||
80 | ||
34 | 81 |
class StudioDirectory(Directory): |
35 |
_q_exports = ['', 'deprecations', ('logged-errors', 'logged_errors_dir')] |
|
82 |
_q_exports = ['', 'deprecations', ('logged-errors', 'logged_errors_dir'), ('all-changes', 'changes_dir')]
|
|
36 | 83 | |
37 | 84 |
deprecations = DeprecationsDirectory() |
85 |
changes_dir = ChangesDirectory() |
|
38 | 86 | |
39 | 87 |
def __init__(self): |
40 | 88 |
self.logged_errors_dir = LoggedErrorsDirectory(parent_dir=self) |
... | ... | |
71 | 119 |
if backoffice_root.is_accessible('cards'): |
72 | 120 |
object_types += [CardDef] |
73 | 121 | |
122 |
user = get_request().user |
|
74 | 123 |
context = { |
75 | 124 |
'has_sidebar': False, |
76 | 125 |
'extra_links': extra_links, |
77 | 126 |
'recent_errors': LoggedErrorsDirectory.get_errors(offset=0, limit=5)[0], |
127 |
'show_all_changes': user and user.is_admin, |
|
78 | 128 |
} |
79 | 129 |
if get_publisher().snapshot_class: |
80 | 130 |
context['recent_objects'] = get_publisher().snapshot_class.get_recent_changes( |
wcs/snapshots.py | ||
---|---|---|
184 | 184 |
obj.store() |
185 | 185 | |
186 | 186 |
@classmethod |
187 |
def get_recent_changes(cls, object_types, user):
|
|
188 |
elements = cls._get_recent_changes(object_types, user)
|
|
187 |
def get_recent_changes(cls, object_types=None, user=None, limit=5, offset=0):
|
|
188 |
elements = cls._get_recent_changes(object_types=object_types, user=user, limit=limit, offset=offset)
|
|
189 | 189 |
instances = [] |
190 | 190 |
for object_type, object_id, snapshot_timestamp in elements: |
191 | 191 |
klass = cls.get_class(object_type) |
wcs/sql.py | ||
---|---|---|
3625 | 3625 |
return cls.get(row[0]) |
3626 | 3626 | |
3627 | 3627 |
@classmethod |
3628 |
def _get_recent_changes(cls, object_types, user): |
|
3628 |
def _get_recent_changes(cls, object_types, user=None, limit=5, offset=0):
|
|
3629 | 3629 |
conn, cur = get_connection_and_cursor() |
3630 |
sql_statement = '''SELECT object_type, object_id, MAX(timestamp) AS m |
|
3631 |
FROM snapshots |
|
3632 |
WHERE object_type IN %(object_types)s |
|
3633 |
AND user_id = %(user_id)s |
|
3634 |
GROUP BY object_type, object_id |
|
3635 |
ORDER BY m DESC |
|
3636 |
LIMIT 5''' |
|
3637 |
parameters = {'object_types': tuple(object_types), 'user_id': str(user.id)} |
|
3630 |
clause = [Contains('object_type', object_types)] |
|
3631 |
if user is not None: |
|
3632 |
clause.append(Equal('user_id', str(user.id))) |
|
3633 |
where_clauses, parameters, dummy = parse_clause(clause) |
|
3634 | ||
3635 |
sql_statement = 'SELECT object_type, object_id, MAX(timestamp) AS m FROM snapshots' |
|
3636 |
sql_statement += ' WHERE ' + ' AND '.join(where_clauses) |
|
3637 |
sql_statement += ' GROUP BY object_type, object_id ORDER BY m DESC' |
|
3638 | ||
3639 |
if limit: |
|
3640 |
sql_statement += ' LIMIT %(limit)s' |
|
3641 |
parameters['limit'] = limit |
|
3642 |
if offset: |
|
3643 |
sql_statement += ' OFFSET %(offset)s' |
|
3644 |
parameters['offset'] = offset |
|
3645 | ||
3638 | 3646 |
cur.execute(sql_statement, parameters) |
3639 | 3647 |
result = cur.fetchall() |
3640 | 3648 |
conn.commit() |
3641 | 3649 |
cur.close() |
3642 | 3650 |
return result |
3643 | 3651 | |
3652 |
@classmethod |
|
3653 |
def count_recent_changes(cls, object_types): |
|
3654 |
conn, cur = get_connection_and_cursor() |
|
3655 | ||
3656 |
clause = [Contains('object_type', object_types)] |
|
3657 |
where_clauses, parameters, dummy = parse_clause(clause) |
|
3658 |
sql_statement = 'SELECT COUNT(*) FROM (SELECT object_type, object_id FROM snapshots' |
|
3659 |
sql_statement += ' WHERE ' + ' AND '.join(where_clauses) |
|
3660 |
sql_statement += ' GROUP BY object_type, object_id) AS s' |
|
3661 | ||
3662 |
cur.execute(sql_statement, parameters) |
|
3663 |
count = cur.fetchone()[0] |
|
3664 |
conn.commit() |
|
3665 |
cur.close() |
|
3666 |
return count |
|
3667 | ||
3644 | 3668 | |
3645 | 3669 |
class LoggedError(SqlMixin, wcs.logged_errors.LoggedError): |
3646 | 3670 |
_table_name = 'loggederrors' |
wcs/templates/wcs/backoffice/changes.html | ||
---|---|---|
1 |
{% extends "wcs/backoffice/base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block appbar-title %}{% trans "All changes" context 'studio' %}{% endblock %} |
|
5 | ||
6 |
{% block content %} |
|
7 |
<ul class="objects-list single-links"> |
|
8 |
{% for obj in objects %} |
|
9 |
<li> |
|
10 |
<a href="{{ obj.get_admin_url }}"> |
|
11 |
{{ obj.name }} |
|
12 |
<span class="extra-info">{{ obj.snapshot_timestamp }}</span> |
|
13 |
<span class="badge">{{ obj.verbose_name }}</span> |
|
14 |
</a> |
|
15 |
</li> |
|
16 |
{% endfor %} |
|
17 |
</ul> |
|
18 |
{{ pagination_links|safe }} |
|
19 |
{% endblock %} |
wcs/templates/wcs/backoffice/studio.html | ||
---|---|---|
40 | 40 |
<span class="timestamp">{{ obj.snapshot_timestamp }}</span></li> |
41 | 41 |
{% endfor %} |
42 | 42 |
</ul> |
43 |
{% if show_all_changes %} |
|
44 |
<p><a class="all-changes pk-button" href="all-changes/">{% trans "See all changes" %}</a></p> |
|
45 |
{% endif %} |
|
43 | 46 |
</div> |
44 | 47 | |
45 | 48 |
<div class="errors-and-deprecations"> |
46 |
- |