0001-snapshot-show-differences-between-2-versions-58798.patch
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 %} {% endif %} |
|
23 |
{% if not forloop.first %}<input type="radio" name="version2" value="{{ snapshot.id }}" {% if forloop.counter == 2 %}checked="checked"{% endif %}/>{% else %} {% 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 |
- |