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: -
- - - - - - - - - % for field in fields_for_version(version): - - - - - - % endfor - -
field nameold valuenew value
${field}${render_old_value(version, field)} 
- % elif version.previous: - - - - - - - - - - % for field in fields_for_version(version): - - - - - - % endfor - -
field nameold valuenew value
${field}${render_old_value(version, field)}${render_new_value(version, field, 'dirty')}
- % else: - - - - - - - - - - % for field in fields_for_version(version): - - - - - - % endfor - -
field nameold valuenew value
${field} ${render_new_value(version, field, 'new')}
- % endif - -% endfor + % for diff in version_diffs: +

${diff.title}

+ ${diff.render_html()} + % endfor + 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}