Projet

Général

Profil

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

Lauréline Guérin, 30 juin 2022 11:55

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: 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
-