Add smarts to show display text for some version diff fields

e.g. show `str(customer)` along with `customer_uuid` since almost
nobody will "care" about the uuid so much, they just want the name
This commit is contained in:
Lance Edgar 2023-10-09 00:19:29 -05:00
parent edb5393cdc
commit 9efe767654
6 changed files with 118 additions and 95 deletions

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2019 Lance Edgar # Copyright © 2010-2023 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -24,7 +24,8 @@
Tools for displaying data diffs 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 pyramid.renderers import render
from webhelpers2.html import HTML from webhelpers2.html import HTML
@ -36,7 +37,7 @@ class Diff(object):
""" """
def __init__(self, old_data, new_data, columns=None, fields=None, 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): monospace=False, extra_row_attrs=None):
""" """
Constructor. You must provide the old and new data sets, and 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.fields = fields or self.make_fields()
self._render_field = render_field or self.render_field_default self._render_field = render_field or self.render_field_default
self.render_value = render_value or self.render_value_default self.render_value = render_value or self.render_value_default
self.nature = nature
self.monospace = monospace self.monospace = monospace
self.extra_row_attrs = extra_row_attrs self.extra_row_attrs = extra_row_attrs
@ -126,3 +128,80 @@ class Diff(object):
def render_new_value(self, field): def render_new_value(self, field):
value = self.new_value(field) value = self.new_value(field)
return self.render_value(field, value) 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,
}

View file

@ -1,5 +1,5 @@
## -*- coding: utf-8; -*- ## -*- coding: utf-8; -*-
<table class="diff dirty${' monospace' if diff.monospace else ''}"> <table class="diff ${diff.nature} ${' monospace' if diff.monospace else ''}">
<thead> <thead>
<tr> <tr>
% for column in diff.columns: % for column in diff.columns:

View file

@ -50,71 +50,12 @@
</div><!-- form-wrapper --> </div><!-- form-wrapper -->
<div class="versions-wrapper"> <div class="versions-wrapper">
% for version in versions: % for diff in version_diffs:
<h2>${diff.title}</h2>
<h2>${title_for_version(version)}</h2> ${diff.render_html()}
% if version.previous and version.operation_type == continuum.Operation.DELETE:
<table class="diff monospace deleted">
<thead>
<tr>
<th>field name</th>
<th>old value</th>
<th>new value</th>
</tr>
</thead>
<tbody>
% for field in fields_for_version(version):
<tr>
<td class="field">${field}</td>
<td class="value old-value">${render_old_value(version, field)}</td>
<td class="value new-value">&nbsp;</td>
</tr>
% endfor
</tbody>
</table>
% elif version.previous:
<table class="diff monospace dirty">
<thead>
<tr>
<th>field name</th>
<th>old value</th>
<th>new value</th>
</tr>
</thead>
<tbody>
% for field in fields_for_version(version):
<tr${' class="diff"' if getattr(version, field) != getattr(version.previous, field) else ''|n}>
<td class="field">${field}</td>
<td class="value old-value">${render_old_value(version, field)}</td>
<td class="value new-value">${render_new_value(version, field, 'dirty')}</td>
</tr>
% endfor
</tbody>
</table>
% else:
<table class="diff monospace new">
<thead>
<tr>
<th>field name</th>
<th>old value</th>
<th>new value</th>
</tr>
</thead>
<tbody>
% for field in fields_for_version(version):
<tr>
<td class="field">${field}</td>
<td class="value old-value">&nbsp;</td>
<td class="value new-value">${render_new_value(version, field, 'new')}</td>
</tr>
% endfor
</tbody>
</table>
% endif
% endfor % endfor
</div> </div>
</%def> </%def>

View file

@ -1456,8 +1456,8 @@
:class="{diff: version.values[field].after != version.values[field].before}" :class="{diff: version.values[field].after != version.values[field].before}"
v-show="revisionShowAllFields || version.values[field].after != version.values[field].before"> v-show="revisionShowAllFields || version.values[field].after != version.values[field].before">
<td class="field">{{ field }}</td> <td class="field">{{ field }}</td>
<td class="old-value">{{ version.values[field].before }}</td> <td class="old-value" v-html="version.values[field].before"></td>
<td class="new-value">{{ version.values[field].after }}</td> <td class="new-value" v-html="version.values[field].after"></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

@ -1361,6 +1361,20 @@ class MasterView(View):
if newer: if newer:
next_url = self.request.route_url('{}.version'.format(route_prefix), uuid=instance.uuid, txnid=newer.id) 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', { return self.render_to_response('view_version', {
'instance': instance, 'instance': instance,
'instance_title': "{} (history)".format(instance_title), 'instance_title': "{} (history)".format(instance_title),
@ -1368,7 +1382,7 @@ class MasterView(View):
'instance_url': self.get_action_url('versions', instance), 'instance_url': self.get_action_url('versions', instance),
'transaction': transaction, 'transaction': transaction,
'changed': localtime(self.rattail_config, transaction.issued_at, from_utc=True), '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, 'show_prev_next': True,
'prev_url': prev_url, 'prev_url': prev_url,
'next_url': next_url, 'next_url': next_url,
@ -4815,6 +4829,11 @@ class MasterView(View):
def make_diff(self, old_data, new_data, **kwargs): def make_diff(self, old_data, new_data, **kwargs):
return diffs.Diff(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 # Configuration Views
############################## ##############################

View file

@ -1398,25 +1398,15 @@ class PersonView(MasterView):
# also organize final transaction/versions (diff) map # also organize final transaction/versions (diff) map
vmap = {} vmap = {}
for version in versions: 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) fields = self.fields_for_version(version)
values = {}
old_data = {}
new_data = {}
for field in fields: for field in fields:
before = '' if version.previous:
after = '' old_data[field] = getattr(version.previous, field)
if diff_class != 'new': new_data[field] = getattr(version, field)
before = repr(getattr(version.previous, field)) diff = self.make_version_diff(version, old_data, new_data, fields=fields)
if diff_class != 'deleted':
after = repr(getattr(version, field))
values[field] = {'before': before, 'after': after}
if version.transaction_id not in vmap: if version.transaction_id not in vmap:
txn = version.transaction txn = version.transaction
@ -1439,13 +1429,7 @@ class PersonView(MasterView):
'versions': [], 'versions': [],
} }
vmap[version.transaction_id]['versions'].append({ vmap[version.transaction_id]['versions'].append(diff.as_struct())
'key': id(version),
'model_title': self.title_for_version(version),
'diff_class': diff_class,
'fields': fields,
'values': values,
})
return {'data': data, 'vmap': vmap} return {'data': data, 'vmap': vmap}