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:
		
							parent
							
								
									edb5393cdc
								
							
						
					
					
						commit
						9efe767654
					
				
					 6 changed files with 118 additions and 95 deletions
				
			
		| 
						 | 
				
			
			@ -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,
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
## -*- coding: utf-8; -*-
 | 
			
		||||
<table class="diff dirty${' monospace' if diff.monospace else ''}">
 | 
			
		||||
<table class="diff ${diff.nature} ${' monospace' if diff.monospace else ''}">
 | 
			
		||||
  <thead>
 | 
			
		||||
    <tr>
 | 
			
		||||
      % for column in diff.columns:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,71 +50,12 @@
 | 
			
		|||
</div><!-- form-wrapper -->
 | 
			
		||||
 | 
			
		||||
<div class="versions-wrapper">
 | 
			
		||||
% for version in versions:
 | 
			
		||||
 | 
			
		||||
    <h2>${title_for_version(version)}</h2>
 | 
			
		||||
 | 
			
		||||
    % 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"> </td>
 | 
			
		||||
               </tr>
 | 
			
		||||
  % for diff in version_diffs:
 | 
			
		||||
      <h2>${diff.title}</h2>
 | 
			
		||||
      ${diff.render_html()}
 | 
			
		||||
  % 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"> </td>
 | 
			
		||||
                 <td class="value new-value">${render_new_value(version, field, 'new')}</td>
 | 
			
		||||
               </tr>
 | 
			
		||||
            % endfor
 | 
			
		||||
          </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
    % endif
 | 
			
		||||
 | 
			
		||||
% endfor
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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">
 | 
			
		||||
                        <td class="field">{{ field }}</td>
 | 
			
		||||
                        <td class="old-value">{{ version.values[field].before }}</td>
 | 
			
		||||
                        <td class="new-value">{{ version.values[field].after }}</td>
 | 
			
		||||
                        <td class="old-value" v-html="version.values[field].before"></td>
 | 
			
		||||
                        <td class="new-value" v-html="version.values[field].after"></td>
 | 
			
		||||
                      </tr>
 | 
			
		||||
                    </tbody>
 | 
			
		||||
                  </table>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
    ##############################
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue