diff --git a/tailbone/diffs.py b/tailbone/diffs.py
index d57aa9ac..431c2efe 100644
--- a/tailbone/diffs.py
+++ b/tailbone/diffs.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2019 Lance Edgar
+# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
@@ -24,7 +24,8 @@
Tools for displaying data diffs
"""
-from __future__ import unicode_literals, absolute_import
+import sqlalchemy as sa
+import sqlalchemy_continuum as continuum
from pyramid.renderers import render
from webhelpers2.html import HTML
@@ -36,7 +37,7 @@ class Diff(object):
"""
def __init__(self, old_data, new_data, columns=None, fields=None,
- render_field=None, render_value=None,
+ render_field=None, render_value=None, nature='dirty',
monospace=False, extra_row_attrs=None):
"""
Constructor. You must provide the old and new data sets, and
@@ -64,6 +65,7 @@ class Diff(object):
self.fields = fields or self.make_fields()
self._render_field = render_field or self.render_field_default
self.render_value = render_value or self.render_value_default
+ self.nature = nature
self.monospace = monospace
self.extra_row_attrs = extra_row_attrs
@@ -126,3 +128,80 @@ class Diff(object):
def render_new_value(self, field):
value = self.new_value(field)
return self.render_value(field, value)
+
+
+class VersionDiff(Diff):
+ """
+ Special diff class, for use with version history views
+ """
+
+ def __init__(self, version, *args, **kwargs):
+ self.title = kwargs.pop('title', None)
+
+ if 'nature' not in kwargs:
+ if version.previous and version.operation_type == continuum.Operation.DELETE:
+ kwargs['nature'] = 'deleted'
+ elif version.previous:
+ kwargs['nature'] = 'dirty'
+ else:
+ kwargs['nature'] = 'new'
+
+ super().__init__(*args, **kwargs)
+
+ self.version = version
+ self.mapper = sa.inspect(continuum.parent_class(type(self.version)))
+
+ def render_version_value(self, field, value, version):
+ text = HTML.tag('span', c=[repr(value)],
+ style='font-family: monospace;')
+
+ for prop in self.mapper.relationships:
+ if prop.uselist:
+ continue
+
+ for col in prop.local_columns:
+ if col.name != field:
+ continue
+
+ if not hasattr(version, prop.key):
+ continue
+
+ if col in self.mapper.primary_key:
+ continue
+
+ ref = getattr(version, prop.key)
+ if ref:
+ ref = ref.version_parent
+ if ref:
+ return HTML.tag('span', c=[
+ text,
+ HTML.tag('span', c=[str(ref)],
+ style='margin-left: 2rem; font-style: italic; font-weight: bold;'),
+ ])
+
+ return text
+
+ def render_old_value(self, field):
+ if self.nature == 'new':
+ return ''
+ value = self.old_value(field)
+ return self.render_version_value(field, value, self.version.previous)
+
+ def render_new_value(self, field):
+ if self.nature == 'deleted':
+ return ''
+ value = self.new_value(field)
+ return self.render_version_value(field, value, self.version)
+
+ def as_struct(self):
+ values = {}
+ for field in self.fields:
+ values[field] = {'before': self.render_old_value(field),
+ 'after': self.render_new_value(field)}
+ return {
+ 'key': id(self.version),
+ 'model_title': self.title,
+ 'diff_class': self.nature,
+ 'fields': self.fields,
+ 'values': values,
+ }
diff --git a/tailbone/templates/diff.mako b/tailbone/templates/diff.mako
index 3e5ec99e..a78bd770 100644
--- a/tailbone/templates/diff.mako
+++ b/tailbone/templates/diff.mako
@@ -1,5 +1,5 @@
## -*- coding: utf-8; -*-
-
+
% for column in diff.columns:
diff --git a/tailbone/templates/master/view_version.mako b/tailbone/templates/master/view_version.mako
index 5dbcd15d..d29a3496 100644
--- a/tailbone/templates/master/view_version.mako
+++ b/tailbone/templates/master/view_version.mako
@@ -50,71 +50,12 @@
-% for version in versions:
-
-
${title_for_version(version)}
-
- % if version.previous and version.operation_type == continuum.Operation.DELETE:
-
-
-
- field name |
- old value |
- new value |
-
-
-
- % for field in fields_for_version(version):
-
- ${field} |
- ${render_old_value(version, field)} |
- |
-
- % endfor
-
-
- % elif version.previous:
-
-
-
- field name |
- old value |
- new value |
-
-
-
- % for field in fields_for_version(version):
-
- ${field} |
- ${render_old_value(version, field)} |
- ${render_new_value(version, field, 'dirty')} |
-
- % endfor
-
-
- % else:
-
-
-
- field name |
- old value |
- new value |
-
-
-
- % for field in fields_for_version(version):
-
- ${field} |
- |
- ${render_new_value(version, field, 'new')} |
-
- % endfor
-
-
- % endif
-
-% endfor
+ % for diff in version_diffs:
+
${diff.title}
+ ${diff.render_html()}
+ % endfor
+
%def>
diff --git a/tailbone/templates/people/view_profile_buefy.mako b/tailbone/templates/people/view_profile_buefy.mako
index 5574088e..4b1e089c 100644
--- a/tailbone/templates/people/view_profile_buefy.mako
+++ b/tailbone/templates/people/view_profile_buefy.mako
@@ -1456,8 +1456,8 @@
:class="{diff: version.values[field].after != version.values[field].before}"
v-show="revisionShowAllFields || version.values[field].after != version.values[field].before">
{{ field }} |
- {{ version.values[field].before }} |
- {{ version.values[field].after }} |
+ |
+ |
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index ac68a02f..167bdace 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -1361,6 +1361,20 @@ class MasterView(View):
if newer:
next_url = self.request.route_url('{}.version'.format(route_prefix), uuid=instance.uuid, txnid=newer.id)
+ version_diffs = []
+ versions = self.get_relevant_versions(transaction, instance)
+ for version in versions:
+
+ old_data = {}
+ new_data = {}
+ fields = self.fields_for_version(version)
+ for field in fields:
+ if version.previous:
+ old_data[field] = getattr(version.previous, field)
+ new_data[field] = getattr(version, field)
+ diff = self.make_version_diff(version, old_data, new_data, fields=fields)
+ version_diffs.append(diff)
+
return self.render_to_response('view_version', {
'instance': instance,
'instance_title': "{} (history)".format(instance_title),
@@ -1368,7 +1382,7 @@ class MasterView(View):
'instance_url': self.get_action_url('versions', instance),
'transaction': transaction,
'changed': localtime(self.rattail_config, transaction.issued_at, from_utc=True),
- 'versions': self.get_relevant_versions(transaction, instance),
+ 'version_diffs': version_diffs,
'show_prev_next': True,
'prev_url': prev_url,
'next_url': next_url,
@@ -4815,6 +4829,11 @@ class MasterView(View):
def make_diff(self, old_data, new_data, **kwargs):
return diffs.Diff(old_data, new_data, **kwargs)
+ def make_version_diff(self, version, old_data, new_data, **kwargs):
+ if 'title' not in kwargs:
+ kwargs['title'] = self.title_for_version(version)
+ return diffs.VersionDiff(version, old_data, new_data, **kwargs)
+
##############################
# Configuration Views
##############################
diff --git a/tailbone/views/people.py b/tailbone/views/people.py
index 0aaf4c26..31760d2a 100644
--- a/tailbone/views/people.py
+++ b/tailbone/views/people.py
@@ -1398,25 +1398,15 @@ class PersonView(MasterView):
# also organize final transaction/versions (diff) map
vmap = {}
for version in versions:
-
- if version.previous and version.operation_type == continuum.Operation.DELETE:
- diff_class = 'deleted'
- elif version.previous:
- diff_class = 'dirty'
- else:
- diff_class = 'new'
-
- # collect before/after field values for version
fields = self.fields_for_version(version)
- values = {}
+
+ old_data = {}
+ new_data = {}
for field in fields:
- before = ''
- after = ''
- if diff_class != 'new':
- before = repr(getattr(version.previous, field))
- if diff_class != 'deleted':
- after = repr(getattr(version, field))
- values[field] = {'before': before, 'after': after}
+ if version.previous:
+ old_data[field] = getattr(version.previous, field)
+ new_data[field] = getattr(version, field)
+ diff = self.make_version_diff(version, old_data, new_data, fields=fields)
if version.transaction_id not in vmap:
txn = version.transaction
@@ -1439,13 +1429,7 @@ class PersonView(MasterView):
'versions': [],
}
- vmap[version.transaction_id]['versions'].append({
- 'key': id(version),
- 'model_title': self.title_for_version(version),
- 'diff_class': diff_class,
- 'fields': fields,
- 'values': values,
- })
+ vmap[version.transaction_id]['versions'].append(diff.as_struct())
return {'data': data, 'vmap': vmap}