Projet

Général

Profil

0001-snapshot-show-differences-between-2-versions-58798.patch

Lauréline Guérin, 20 novembre 2021 18:28

Télécharger (12,7 ko)

Voir les différences:

Subject: [PATCH] snapshot: show differences between 2 versions (#58798)

 tests/test_snapshots.py                       | 90 ++++++++++++++++++-
 wcs/backoffice/snapshots.py                   | 54 ++++++++++-
 wcs/qommon/static/css/dc2/admin.scss          | 25 ++++++
 wcs/snapshots.py                              |  9 +-
 wcs/templates/wcs/backoffice/snapshots.html   | 20 ++++-
 .../wcs/backoffice/snapshots_compare.html     | 11 +++
 6 files changed, 201 insertions(+), 8 deletions(-)
 create mode 100644 wcs/templates/wcs/backoffice/snapshots_compare.html
tests/test_snapshots.py
152 152
    assert [int(f.id) for f in snapshot6.instance.fields] == list(range(0, 11))
153 153

  
154 154

  
155
def test_snapshot_diff(pub):
156
    create_superuser(pub)
157
    create_role(pub)
158

  
159
    formdef = FormDef()
160
    formdef.name = 'testform'
161
    formdef.fields = []
162
    formdef.store()
163
    assert pub.snapshot_class.count() == 1
164
    snapshot1 = pub.snapshot_class.get_latest('formdef', formdef.id)
165

  
166
    formdef.fields = [StringField(id=1, label='Test', type='string')]
167
    formdef.store()
168
    assert pub.snapshot_class.count() == 2
169
    snapshot2 = pub.snapshot_class.get_latest('formdef', formdef.id)
170

  
171
    formdef.fields += [StringField(id=2, label='Test bis', type='string')]
172
    formdef.store()
173
    assert pub.snapshot_class.count() == 3
174
    snapshot3 = pub.snapshot_class.get_latest('formdef', formdef.id)
175

  
176
    app = login(get_app(pub))
177
    resp = app.get('/backoffice/forms/%s/history/' % formdef.id)
178
    assert 'name="version1" value="%s"' % snapshot3.id in resp
179
    assert 'name="version2" value="%s"' % snapshot3.id not in resp
180
    assert 'name="version1" value="%s"' % snapshot2.id in resp
181
    assert 'name="version2" value="%s"' % snapshot2.id in resp
182
    assert 'name="version1" value="%s"' % snapshot1.id not in resp
183
    assert 'name="version2" value="%s"' % snapshot1.id in resp
184

  
185
    resp = app.get(
186
        '/backoffice/forms/%s/history/compare?version1=%s&version2=%s'
187
        % (formdef.id, snapshot1.id, snapshot3.id)
188
    )
189
    assert 'Snapshot <a href="%s/view/">%s</a>' % (snapshot1.id, snapshot1.id) in resp
190
    assert 'Snapshot <a href="%s/view/">%s</a>' % (snapshot3.id, snapshot3.id) in resp
191
    assert resp.text.count('diff_sub') == 1
192
    assert resp.text.count('diff_add') == 24
193

  
194
    resp = app.get(
195
        '/backoffice/forms/%s/history/compare?version1=%s&version2=%s'
196
        % (formdef.id, snapshot3.id, snapshot1.id)
197
    )
198
    assert 'Snapshot <a href="%s/view/">%s</a>' % (snapshot1.id, snapshot1.id) in resp
199
    assert 'Snapshot <a href="%s/view/">%s</a>' % (snapshot3.id, snapshot3.id) in resp
200
    assert resp.text.count('diff_sub') == 1
201
    assert resp.text.count('diff_add') == 24
202

  
203
    resp = app.get(
204
        '/backoffice/forms/%s/history/compare?version1=%s&version2=%s'
205
        % (formdef.id, snapshot2.id, snapshot3.id)
206
    )
207
    assert 'Snapshot <a href="%s/view/">%s</a>' % (snapshot2.id, snapshot2.id) in resp
208
    assert 'Snapshot <a href="%s/view/">%s</a>' % (snapshot3.id, snapshot3.id) in resp
209
    assert resp.text.count('diff_sub') == 0
210
    assert resp.text.count('diff_add') == 11
211

  
212
    formdef.fields = [StringField(id=1, label='Test', type='string')]
213
    formdef.store()
214
    assert pub.snapshot_class.count() == 4
215
    snapshot4 = pub.snapshot_class.get_latest('formdef', formdef.id)
216

  
217
    resp = app.get(
218
        '/backoffice/forms/%s/history/compare?version1=%s&version2=%s'
219
        % (formdef.id, snapshot3.id, snapshot4.id)
220
    )
221
    assert 'Snapshot <a href="%s/view/">%s</a>' % (snapshot3.id, snapshot3.id) in resp
222
    assert 'Snapshot <a href="%s/view/">%s</a>' % (snapshot4.id, snapshot4.id) in resp
223
    assert resp.text.count('diff_sub') == 11
224
    assert resp.text.count('diff_add') == 0
225

  
226
    resp = app.get('/backoffice/forms/%s/history/compare' % (formdef.id), status=404)
227
    resp = app.get(
228
        '/backoffice/forms/%s/history/compare?version1=%s' % (formdef.id, snapshot4.id), status=404
229
    )
230
    resp = app.get(
231
        '/backoffice/forms/%s/history/compare?version2=%s' % (formdef.id, snapshot4.id), status=404
232
    )
233
    resp = app.get(
234
        '/backoffice/forms/%s/history/compare?version1=%s&version2=%s' % (formdef.id, snapshot3.id, 0),
235
        status=404,
236
    )
237
    resp = app.get(
238
        '/backoffice/forms/%s/history/compare?version1=%s&version2=%s' % (formdef.id, 0, snapshot4.id),
239
        status=404,
240
    )
241

  
242

  
155 243
def test_snapshot_instance(pub):
156 244
    formdef = FormDef()
157 245
    formdef.name = 'testform'
......
806 894
    resp = app.get('/backoffice/workflows/%s/history/' % workflow.id)
807 895
    comments = [
808 896
        x.text[18 : x.text.find('\n')]
809
        for x in resp.html.find('ul', {'class': 'snapshots-list'}).find_all('li')
897
        for x in resp.html.find('ul', {'class': 'snapshots-list'}).find_all('span', {'class': 'label'})
810 898
    ]
811 899
    assert comments == [
812 900
        'Deletion of action "Webservice (foo)" in status "baz"',
wcs/backoffice/snapshots.py
14 14
# You should have received a copy of the GNU General Public License
15 15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16 16

  
17
from quixote import get_publisher, get_response, get_session, redirect
17
import difflib
18

  
19
from quixote import get_publisher, get_request, get_response, get_session, redirect
18 20
from quixote.directory import Directory
19 21
from quixote.html import TemplateIO, htmltext
20 22

  
......
31 33

  
32 34

  
33 35
class SnapshotsDirectory(Directory):
34
    _q_exports = ['', 'save']
36
    _q_exports = ['', 'save', 'compare']
35 37
    do_not_call_in_templates = True
36 38

  
37 39
    def __init__(self, instance):
......
68 70
        r += form.render()
69 71
        return r.getvalue()
70 72

  
73
    def compare(self):
74
        get_response().breadcrumb.append(('compare/', _('Compare')))
75
        html_top('', _('Compare'))
76

  
77
        id1 = get_request().form.get('version1')
78
        id2 = get_request().form.get('version2')
79
        if not id1 or not id2:
80
            raise errors.TraversalError()
81

  
82
        snapshot1 = get_publisher().snapshot_class.get(id1, ignore_errors=True)
83
        snapshot2 = get_publisher().snapshot_class.get(id2, ignore_errors=True)
84
        if not snapshot1 or not snapshot2:
85
            raise errors.TraversalError()
86
        if snapshot1.timestamp > snapshot2.timestamp:
87
            snapshot1, snapshot2 = snapshot2, snapshot1
88

  
89
        def snapshot_desc(snapshot):
90
            label_or_comment = ''
91
            if snapshot.label:
92
                label_or_comment = snapshot.label
93
            elif snapshot.comment:
94
                label_or_comment = snapshot.comment
95
            return '{name} <a href="{pk}/view/">{pk}</a><br />{label_or_comment}<br />({user}{timestamp})'.format(
96
                name=_('Snapshot'),
97
                pk=snapshot.id,
98
                label_or_comment=label_or_comment,
99
                user='%s ' % snapshot.user if snapshot.user_id else '',
100
                timestamp=misc.strftime(misc.datetime_format(), snapshot.timestamp),
101
            )
102

  
103
        serialization1 = snapshot1.get_serialization(indented=True)
104
        serialization2 = snapshot2.get_serialization(indented=True)
105
        diff_serialization = difflib.HtmlDiff(wrapcolumn=160).make_table(
106
            fromlines=serialization1.splitlines(True),
107
            tolines=serialization2.splitlines(True),
108
            fromdesc=snapshot_desc(snapshot1),
109
            todesc=snapshot_desc(snapshot2),
110
        )
111

  
112
        return template.QommonTemplateResponse(
113
            templates=['wcs/backoffice/snapshots_compare.html'],
114
            context={
115
                'snapshot1': snapshot1,
116
                'snapshot2': snapshot2,
117
                'diff_serialization': diff_serialization,
118
            },
119
        )
120

  
71 121
    def snapshots(self):
72 122
        current_date = None
73 123
        snapshots = get_publisher().snapshot_class.select_object_history(self.obj)
wcs/qommon/static/css/dc2/admin.scss
2209 2209
		box-shadow: -0.5em -0.5em 0 .5em hsla(0, 0%, 0%, 0.05);
2210 2210
	}
2211 2211
}
2212

  
2213
table.diff {
2214
	border: 1px solid black;
2215
	width: 100%;
2216
	.diff_header {
2217
		background-color: #e0e0e0;
2218
	}
2219
	td.diff_header {
2220
		text-align: right;
2221
		padding-right: 10px;
2222
		color: #606060;
2223
	}
2224
	.diff_next {
2225
		display: none;
2226
	}
2227
	.diff_add {
2228
		background-color: #aaffaa;
2229
	}
2230
	.diff_chg {
2231
		background-color: #ffff77;
2232
	}
2233
	.diff_sub {
2234
		background-color: #ffaaaa;
2235
	}
2236
}
wcs/snapshots.py
196 196
                return klass
197 197
        raise KeyError('no class for object type: %s' % self.object_type)
198 198

  
199
    def get_serialization(self):
199
    def get_serialization(self, indented=True):
200 200
        # there is a complete serialization
201 201
        if self.serialization:
202
            return self.serialization
202
            if not indented:
203
                return self.serialization
204

  
205
            tree = ET.fromstring(self.serialization)
206
            indent(tree)
207
            return ET.tostring(tree).decode('utf-8')
203 208

  
204 209
        # get latest version with serialization
205 210
        latest_complete = self.__class__.get_latest(
wcs/templates/wcs/backoffice/snapshots.html
10 10
{% with snapshots=view.snapshots %}
11 11
{% if snapshots %}
12 12
<div class="section">
13
<form action="compare" method="get">
14
{% if snapshots|length > 1 %}
15
<input type="submit" value="{% trans "Show differences" %}"/>
16
{% endif %}
13 17
<ul class="objects-list snapshots-list">
14 18
{% for snapshot in snapshots %}
15
<li data-day="{{ snapshot.timestamp|date:"Y-m-d" }}" class="{% if snapshot.new_day %}new-day{% elif snapshot.label %}has-label{% else %}collapsed{% endif %}"
16
        >{{ snapshot.timestamp }}{% if snapshot.label %} <strong>{{ snapshot.label }}</strong>{% elif snapshot.comment %}, {{ snapshot.comment }}{% endif %}
17
    {% if snapshot.user_id %}({{ snapshot.user }}){% endif %}
19
<li data-day="{{ snapshot.timestamp|date:"Y-m-d" }}" class="{% if snapshot.new_day %}new-day{% elif snapshot.label %}has-label{% else %}collapsed{% endif %}">
20
    <span class="counter">#{{ snapshot.id }}</span>
21
    {% if snapshots|length > 1 %}
22
      {% if not forloop.last %}<input type="radio" name="version1" value="{{ snapshot.id }}" {% if forloop.first %}checked="checked"{% endif %} />{% else %}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{% endif %}
23
      {% if not forloop.first %}<input type="radio" name="version2" value="{{ snapshot.id }}" {% if forloop.counter == 2 %}checked="checked"{% endif %}/>{% else %}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{% endif %}
24
    {% endif %}
25
    <span class="label">{{ snapshot.timestamp }}{% if snapshot.label %} <strong>{{ snapshot.label }}</strong>{% elif snapshot.comment %}, {{ snapshot.comment }}{% endif %}
26
        {% if snapshot.user_id %}({{ snapshot.user }}){% endif %}</span>
18 27
    {% if snapshot.new_day and snapshot.day_other_count %}(<a class="reveal"
19 28
            href="#day-{{ snapshot.timestamp|date:"Y-m-d"}}">
20 29
    {% if snapshot.day_other_count >= 50 %}<strong>{% endif %}
......
33 42
</li>
34 43
{% endfor %}
35 44
</ul>
45
</form>
36 46
</div>
37 47
{% else %}
38 48
<div class="infonotice"><p>{% trans "No changes history" %}</p></div>
......
47 57
    $('.snapshots-list li[data-day="' + day + '"]').toggleClass('collapsed');
48 58
    return false;
49 59
  });
60
  $('input[name="version1"]').on('click', function() {
61
    var next = $(this).parent('li').next();
62
    $('input[name="version2"]', next).prop('checked', true);
63
  });
50 64
});
51 65
</script>
52 66

  
wcs/templates/wcs/backoffice/snapshots_compare.html
1
{% load i18n %}
2

  
3
{% block body %}
4
<div id="appbar">
5
<h2>{% trans "Compare snapshots" %}</h2>
6
</div>
7

  
8
<div class="section">
9
  {{ diff_serialization|safe }}
10
</div>
11
{% endblock %}
0
-