studio: admin can see all changes (#62953)
gitea-wip/wcs/pipeline/head Build started... Details

This commit is contained in:
Lauréline Guérin 2022-06-29 22:11:19 +02:00
parent e56c295643
commit 5e922bcfc8
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
6 changed files with 130 additions and 17 deletions

View File

@ -208,7 +208,6 @@ def test_studio_home_recent_changes(pub):
pub.cfg['admin-permissions'].update({'settings': ['x']})
pub.write_cfg()
app = login(get_app(pub))
resp = app.get('/backoffice/studio/')
# no access to settings
for i in range(6):
@ -246,7 +245,6 @@ def test_studio_home_recent_changes(pub):
pub.cfg['admin-permissions'].update({'settings': ['x'], 'forms': ['x']})
pub.write_cfg()
app = login(get_app(pub))
resp = app.get('/backoffice/studio/')
# no access to settings or forms
for i in range(6):
@ -282,7 +280,6 @@ def test_studio_home_recent_changes(pub):
pub.cfg['admin-permissions'].update({'settings': ['x'], 'forms': ['x'], 'workflows': ['x']})
pub.write_cfg()
app = login(get_app(pub))
resp = app.get('/backoffice/studio/')
# no access to settings, forms or workflows
for i in range(6):
@ -307,7 +304,6 @@ def test_studio_home_recent_changes(pub):
assert 'backoffice/cards/%s/' % objects[CardDef.xml_root_node][i].id in resp
objects[CardDef.xml_root_node][5].remove_self()
app = login(get_app(pub))
resp = app.get('/backoffice/studio/')
# too old
assert 'backoffice/cards/%s/' % objects[CardDef.xml_root_node][0].id not in resp
@ -317,6 +313,27 @@ def test_studio_home_recent_changes(pub):
# deleted
assert 'backoffice/cards/%s/' % objects[CardDef.xml_root_node][5].id not in resp
# all changes page: admin user can see all changes (depending on permissions)
resp = resp.click(href='all-changes/')
assert '(1-6/6)' in resp
# he can also see changes from other users
for snapshot in pub.snapshot_class.select():
snapshot.user_id = other_user.id
snapshot.store()
pub.cfg['admin-permissions'] = {}
pub.write_cfg()
resp = app.get('/backoffice/studio/all-changes/')
assert '(1-20/42)' in resp
resp = resp.click('<!--Next Page-->')
assert '21-40/42' in resp.text
resp = resp.click('<!--Next Page-->')
assert '41-42/42' in resp.text
user.is_admin = False
user.store()
app.get('/backoffice/studio/all-changes/', status=403)
def test_studio_workflows(pub):
create_superuser(pub)

View File

@ -24,17 +24,65 @@ from wcs.carddef import CardDef
from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef
from wcs.mail_templates import MailTemplate
from wcs.qommon import _, pgettext, template
from wcs.qommon import _, misc, pgettext, template
from wcs.qommon.backoffice.listing import pagination_links
from wcs.qommon.backoffice.menu import html_top
from wcs.qommon.form import get_response
from wcs.workflows import Workflow
from wcs.wscalls import NamedWsCall
class ChangesDirectory(Directory):
_q_exports = ['']
def _q_index(self):
get_response().breadcrumb.append(('all-changes/', pgettext('studio', 'All changes')))
html_top(pgettext('studio', 'All Changes'))
limit = misc.get_int_or_400(
get_request().form.get('limit', get_publisher().get_site_option('default-page-size')) or 20
)
offset = misc.get_int_or_400(get_request().form.get('offset', 0))
backoffice_root = get_publisher().get_backoffice_root()
object_types = []
if backoffice_root.is_accessible('workflows'):
object_types += [Workflow, MailTemplate]
if backoffice_root.is_accessible('forms'):
object_types += [NamedDataSource, BlockDef, FormDef]
if backoffice_root.is_accessible('workflows'):
object_types += [NamedDataSource]
if backoffice_root.is_accessible('settings'):
object_types += [NamedDataSource, NamedWsCall]
if backoffice_root.is_accessible('cards'):
object_types += [CardDef]
object_types = [ot.xml_root_node for ot in object_types]
objects = []
links = ''
if get_publisher().snapshot_class:
objects = get_publisher().snapshot_class.get_recent_changes(
object_types=object_types, limit=limit, offset=offset
)
total_count = get_publisher().snapshot_class.count_recent_changes(object_types=object_types)
links = pagination_links(offset, limit, total_count, load_js=False)
return template.QommonTemplateResponse(
templates=['wcs/backoffice/changes.html'],
context={
'objects': objects,
'pagination_links': links,
},
)
def is_accessible(self, user):
return user.is_admin
class StudioDirectory(Directory):
_q_exports = ['', 'deprecations', ('logged-errors', 'logged_errors_dir')]
_q_exports = ['', 'deprecations', ('logged-errors', 'logged_errors_dir'), ('all-changes', 'changes_dir')]
deprecations = DeprecationsDirectory()
changes_dir = ChangesDirectory()
def __init__(self):
self.logged_errors_dir = LoggedErrorsDirectory(parent_dir=self)
@ -71,10 +119,12 @@ class StudioDirectory(Directory):
if backoffice_root.is_accessible('cards'):
object_types += [CardDef]
user = get_request().user
context = {
'has_sidebar': False,
'extra_links': extra_links,
'recent_errors': LoggedErrorsDirectory.get_errors(offset=0, limit=5)[0],
'show_all_changes': get_publisher().snapshot_class and user and user.is_admin,
}
if get_publisher().snapshot_class:
context['recent_objects'] = get_publisher().snapshot_class.get_recent_changes(

View File

@ -184,8 +184,8 @@ class Snapshot:
obj.store()
@classmethod
def get_recent_changes(cls, object_types, user):
elements = cls._get_recent_changes(object_types, user)
def get_recent_changes(cls, object_types=None, user=None, limit=5, offset=0):
elements = cls._get_recent_changes(object_types=object_types, user=user, limit=limit, offset=offset)
instances = []
for object_type, object_id, snapshot_timestamp in elements:
klass = cls.get_class(object_type)

View File

@ -3793,22 +3793,46 @@ class Snapshot(SqlMixin, wcs.snapshots.Snapshot):
return cls.get(row[0])
@classmethod
def _get_recent_changes(cls, object_types, user):
def _get_recent_changes(cls, object_types, user=None, limit=5, offset=0):
conn, cur = get_connection_and_cursor()
sql_statement = '''SELECT object_type, object_id, MAX(timestamp) AS m
FROM snapshots
WHERE object_type IN %(object_types)s
AND user_id = %(user_id)s
GROUP BY object_type, object_id
ORDER BY m DESC
LIMIT 5'''
parameters = {'object_types': tuple(object_types), 'user_id': str(user.id)}
clause = [Contains('object_type', object_types)]
if user is not None:
clause.append(Equal('user_id', str(user.id)))
where_clauses, parameters, dummy = parse_clause(clause)
sql_statement = 'SELECT object_type, object_id, MAX(timestamp) AS m FROM snapshots'
sql_statement += ' WHERE ' + ' AND '.join(where_clauses)
sql_statement += ' GROUP BY object_type, object_id ORDER BY m DESC'
if limit:
sql_statement += ' LIMIT %(limit)s'
parameters['limit'] = limit
if offset:
sql_statement += ' OFFSET %(offset)s'
parameters['offset'] = offset
cur.execute(sql_statement, parameters)
result = cur.fetchall()
conn.commit()
cur.close()
return result
@classmethod
def count_recent_changes(cls, object_types):
conn, cur = get_connection_and_cursor()
clause = [Contains('object_type', object_types)]
where_clauses, parameters, dummy = parse_clause(clause)
sql_statement = 'SELECT COUNT(*) FROM (SELECT object_type, object_id FROM snapshots'
sql_statement += ' WHERE ' + ' AND '.join(where_clauses)
sql_statement += ' GROUP BY object_type, object_id) AS s'
cur.execute(sql_statement, parameters)
count = cur.fetchone()[0]
conn.commit()
cur.close()
return count
class LoggedError(SqlMixin, wcs.logged_errors.LoggedError):
_table_name = 'loggederrors'

View File

@ -0,0 +1,19 @@
{% extends "wcs/backoffice/base.html" %}
{% load i18n %}
{% block appbar-title %}{% trans "All changes" context 'studio' %}{% endblock %}
{% block content %}
<ul class="objects-list single-links">
{% for obj in objects %}
<li>
<a href="{{ obj.get_admin_url }}">
{{ obj.name }}
<span class="extra-info">{{ obj.snapshot_timestamp }}</span>
<span class="badge">{{ obj.verbose_name }}</span>
</a>
</li>
{% endfor %}
</ul>
{{ pagination_links|safe }}
{% endblock %}

View File

@ -40,6 +40,9 @@
<span class="timestamp">{{ obj.snapshot_timestamp }}</span></li>
{% endfor %}
</ul>
{% if show_all_changes %}
<p><a class="all-changes pk-button" href="all-changes/">{% trans "See all changes" %}</a></p>
{% endif %}
</div>
<div class="errors-and-deprecations">