Projet

Général

Profil

0002-studio-admin-can-see-all-changes-62953.patch

Lauréline Guérin, 05 juillet 2022 10:37

Télécharger (10,9 ko)

Voir les différences:

Subject: [PATCH 2/2] studio: admin can see all changes (#62953)

 tests/admin_pages/test_studio.py          | 25 +++++++++--
 wcs/backoffice/studio.py                  | 54 ++++++++++++++++++++++-
 wcs/snapshots.py                          |  4 +-
 wcs/sql.py                                | 42 ++++++++++++++----
 wcs/templates/wcs/backoffice/changes.html | 19 ++++++++
 wcs/templates/wcs/backoffice/studio.html  |  3 ++
 6 files changed, 130 insertions(+), 17 deletions(-)
 create mode 100644 wcs/templates/wcs/backoffice/changes.html
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: admin user can see all changes (depending 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': get_publisher().snapshot_class and 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
3788 3788
        return cls.get(row[0])
3789 3789

  
3790 3790
    @classmethod
3791
    def _get_recent_changes(cls, object_types, user):
3791
    def _get_recent_changes(cls, object_types, user=None, limit=5, offset=0):
3792 3792
        conn, cur = get_connection_and_cursor()
3793
        sql_statement = '''SELECT object_type, object_id, MAX(timestamp) AS m
3794
                           FROM snapshots
3795
                           WHERE object_type IN %(object_types)s
3796
                           AND user_id = %(user_id)s
3797
                           GROUP BY object_type, object_id
3798
                           ORDER BY m DESC
3799
                           LIMIT 5'''
3800
        parameters = {'object_types': tuple(object_types), 'user_id': str(user.id)}
3793
        clause = [Contains('object_type', object_types)]
3794
        if user is not None:
3795
            clause.append(Equal('user_id', str(user.id)))
3796
        where_clauses, parameters, dummy = parse_clause(clause)
3797

  
3798
        sql_statement = 'SELECT object_type, object_id, MAX(timestamp) AS m FROM snapshots'
3799
        sql_statement += ' WHERE ' + ' AND '.join(where_clauses)
3800
        sql_statement += ' GROUP BY object_type, object_id ORDER BY m DESC'
3801

  
3802
        if limit:
3803
            sql_statement += ' LIMIT %(limit)s'
3804
            parameters['limit'] = limit
3805
        if offset:
3806
            sql_statement += ' OFFSET %(offset)s'
3807
            parameters['offset'] = offset
3808

  
3801 3809
        cur.execute(sql_statement, parameters)
3802 3810
        result = cur.fetchall()
3803 3811
        conn.commit()
3804 3812
        cur.close()
3805 3813
        return result
3806 3814

  
3815
    @classmethod
3816
    def count_recent_changes(cls, object_types):
3817
        conn, cur = get_connection_and_cursor()
3818

  
3819
        clause = [Contains('object_type', object_types)]
3820
        where_clauses, parameters, dummy = parse_clause(clause)
3821
        sql_statement = 'SELECT COUNT(*) FROM (SELECT object_type, object_id FROM snapshots'
3822
        sql_statement += ' WHERE ' + ' AND '.join(where_clauses)
3823
        sql_statement += ' GROUP BY object_type, object_id) AS s'
3824

  
3825
        cur.execute(sql_statement, parameters)
3826
        count = cur.fetchone()[0]
3827
        conn.commit()
3828
        cur.close()
3829
        return count
3830

  
3807 3831

  
3808 3832
class LoggedError(SqlMixin, wcs.logged_errors.LoggedError):
3809 3833
    _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
-