Add support for version history in person profile view

yay, finally
This commit is contained in:
Lance Edgar 2023-06-06 16:37:58 -05:00
parent 816e652357
commit cfdb492349
2 changed files with 448 additions and 12 deletions

View file

@ -30,6 +30,7 @@ from collections import OrderedDict
import sqlalchemy as sa
from sqlalchemy import orm
import sqlalchemy_continuum as continuum
from rattail.db import model, api
from rattail.db.util import maxlen
@ -42,6 +43,7 @@ from webhelpers2.html import HTML, tags
from tailbone import forms, grids
from tailbone.views import MasterView
from tailbone.util import raw_datetime
log = logging.getLogger(__name__)
@ -429,6 +431,9 @@ class PersonView(MasterView):
'dynamic_content_title': self.get_context_content_title(person),
}
if self.request.has_perm('people_profile.view_versions'):
context['revisions_grid'] = self.profile_revisions_grid(person)
template = 'view_profile_buefy'
return self.render_to_response(template, context)
@ -1015,6 +1020,188 @@ class PersonView(MasterView):
'employee': self.get_context_employee(employee),
}
def profile_revisions_grid(self, person):
route_prefix = self.get_route_prefix()
factory = self.get_grid_factory()
g = factory(
'{}.profile.revisions'.format(route_prefix),
[], # start with empty data!
request=self.request,
columns=[
'changed',
'changed_by',
'remote_addr',
'comment',
],
labels={
'remote_addr': "IP Address",
},
linked_columns=[
'changed',
'changed_by',
'comment',
],
main_actions=[
self.make_action('view', icon='eye', url='#',
click_handler='viewRevision(props.row)'),
],
)
return g
def profile_revisions_collect(self, person, versions=None):
model = self.model
versions = versions or []
# Person
cls = continuum.version_class(model.Person)
query = self.Session.query(cls)\
.filter(cls.uuid == person.uuid)
versions.extend(query.all())
# User
cls = continuum.version_class(model.User)
query = self.Session.query(cls)\
.filter(cls.person_uuid == person.uuid)
versions.extend(query.all())
# Member
cls = continuum.version_class(model.Member)
query = self.Session.query(cls)\
.filter(cls.person_uuid == person.uuid)
versions.extend(query.all())
# Employee
cls = continuum.version_class(model.Employee)
query = self.Session.query(cls)\
.filter(cls.person_uuid == person.uuid)
versions.extend(query.all())
# EmployeeHistory
cls = continuum.version_class(model.EmployeeHistory)
query = self.Session.query(cls)\
.join(model.Employee,
model.Employee.uuid == cls.employee_uuid)\
.filter(model.Employee.person_uuid == person.uuid)
versions.extend(query.all())
# PersonPhoneNumber
cls = continuum.version_class(model.PersonPhoneNumber)
query = self.Session.query(cls)\
.filter(cls.parent_uuid == person.uuid)
versions.extend(query.all())
# PersonEmailAddress
cls = continuum.version_class(model.PersonEmailAddress)
query = self.Session.query(cls)\
.filter(cls.parent_uuid == person.uuid)
versions.extend(query.all())
# PersonMailingAddress
cls = continuum.version_class(model.PersonMailingAddress)
query = self.Session.query(cls)\
.filter(cls.parent_uuid == person.uuid)
versions.extend(query.all())
# CustomerPerson
cls = continuum.version_class(model.CustomerPerson)
query = self.Session.query(cls)\
.filter(cls.person_uuid == person.uuid)
versions.extend(query.all())
# Customer
cls = continuum.version_class(model.Customer)
query = self.Session.query(cls)\
.join(model.CustomerPerson, model.CustomerPerson.customer_uuid == cls.uuid)\
.filter(model.CustomerPerson.person_uuid == person.uuid)
versions.extend(query.all())
# PersonNote
cls = continuum.version_class(model.PersonNote)
query = self.Session.query(cls)\
.filter(cls.parent_uuid == person.uuid)
versions.extend(query.all())
return versions
def profile_revisions_data(self):
"""
View which locates and organizes all relevant "transaction"
(version) history data for a given Person. Returns JSON, for
use with the Buefy table element on the full profile view.
"""
person = self.get_instance()
versions = self.profile_revisions_collect(person)
# organize final table data
data = []
all_txns = set([v.transaction for v in versions])
for i, txn in enumerate(
sorted(all_txns, key=lambda txn: txn.issued_at, reverse=True),
1):
data.append({
'txnid': txn.id,
'changed': raw_datetime(self.rattail_config, txn.issued_at),
'changed_by': str(txn.user or '') or None,
'remote_addr': txn.remote_addr,
'comment': txn.meta.get('comment'),
})
# also stash the sequential index for this transaction, for use later
txn._sequential_index = i
# 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 = {}
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.transaction_id not in vmap:
txn = version.transaction
prev_txnid = None
next_txnid = None
if txn._sequential_index < len(data):
prev_txnid = data[txn._sequential_index]['txnid']
if txn._sequential_index > 1:
next_txnid = data[txn._sequential_index - 2]['txnid']
vmap[txn.id] = {
'index': txn._sequential_index,
'txnid': txn.id,
'prev_txnid': prev_txnid,
'next_txnid': next_txnid,
'changed': raw_datetime(self.rattail_config, txn.issued_at,
verbose=True),
'changed_by': str(txn.user or '') or None,
'remote_addr': txn.remote_addr,
'comment': txn.meta.get('comment'),
'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,
})
return {'data': data, 'vmap': vmap}
def make_note_form(self, mode, person):
schema = NoteSchema().bind(session=self.Session(),
person_uuid=person.uuid)
@ -1269,6 +1456,18 @@ class PersonView(MasterView):
renderer='json',
permission='employees.edit')
# profile - revisions data
config.add_tailbone_permission('people_profile',
'people_profile.view_versions',
"View full version history for a profile")
config.add_route(f'{route_prefix}.view_profile_revisions',
f'{instance_url_prefix}/profile/revisions',
request_method='GET')
config.add_view(cls, attr='profile_revisions_data',
route_name=f'{route_prefix}.view_profile_revisions',
permission='people_profile.view_versions',
renderer='json')
# manage notes from profile view
if cls.manage_notes_from_profile_view: