Show full version history within the "view" page

avoid full page loads when navigating version history
This commit is contained in:
Lance Edgar 2023-10-10 10:54:16 -05:00
parent 44112a3a4b
commit 4328b9e385
9 changed files with 498 additions and 130 deletions

View file

@ -1172,6 +1172,12 @@ class MasterView(View):
context['rows_grid'] = grid
context['rows_grid_tools'] = HTML(self.make_row_grid_tools(instance) or '').strip()
context['expose_versions'] = (self.has_versions
and self.request.rattail_config.versioning_enabled()
and self.has_perm('versions'))
if context['expose_versions']:
context['versions_grid'] = self.make_revisions_grid(instance, empty_data=True)
return self.render_to_response('view', context)
def image(self):
@ -1300,7 +1306,7 @@ class MasterView(View):
return cls.version_grid_key
return '{}.history'.format(cls.get_route_prefix())
def get_version_data(self, instance):
def get_version_data(self, instance, order_by=True):
"""
Generate the base data set for the version grid.
"""
@ -1308,7 +1314,9 @@ class MasterView(View):
transaction_class = continuum.transaction_class(model_class)
query = model_transaction_query(self.Session(), instance, model_class,
child_classes=self.normalize_version_child_classes())
return query.order_by(transaction_class.issued_at.desc())
if order_by:
query = query.order_by(transaction_class.issued_at.desc())
return query
def get_version_child_classes(self):
"""
@ -1330,6 +1338,114 @@ class MasterView(View):
classes.append(cls)
return classes
def make_revisions_grid(self, obj, empty_data=False):
route_prefix = self.get_route_prefix()
row_url = lambda txn, i: self.request.route_url(f'{route_prefix}.version',
uuid=obj.uuid,
txnid=txn.id)
kwargs = {
'component': 'versions-grid',
'ajax_data_url': self.get_action_url('revisions_data', obj),
'sortable': True,
'default_sortkey': 'changed',
'default_sortdir': 'desc',
'main_actions': [
self.make_action('view', icon='eye', url='#',
click_handler='viewRevision(props.row)'),
self.make_action('view_separate', url=row_url, target='_blank',
icon='external-link-alt', ),
],
}
if empty_data:
# TODO: surely there is a better way to have empty initial
# data..? but so much logic depends on a query, can't
# just pass empty list here
txn_class = continuum.transaction_class(self.get_model_class())
meta_class = continuum.versioning_manager.transaction_meta_cls
kwargs['data'] = self.Session.query(txn_class)\
.outerjoin(meta_class,
meta_class.transaction_id == txn_class.id)\
.filter(txn_class.id == -1)
else:
kwargs['data'] = self.get_version_data(obj, order_by=False)
grid = self.make_version_grid(**kwargs)
grid.set_joiner('user', lambda q: q.outerjoin(self.model.User))
grid.set_sorter('user', self.model.User.username)
grid.set_link('remote_addr')
grid.append('id')
grid.set_label('id', "TXN ID")
grid.set_link('id')
return grid
def revisions_data(self):
"""
AJAX view to fetch revision data for current instance.
"""
txnid = self.request.GET.get('txnid')
if txnid:
# return single txn data
app = self.get_rattail_app()
obj = self.get_instance()
cls = self.get_model_class()
txn_cls = continuum.transaction_class(cls)
route_prefix = self.get_route_prefix()
transactions = model_transaction_query(
self.Session(), obj, cls,
child_classes=self.normalize_version_child_classes())
txn = transactions.filter(txn_cls.id == txnid).first()
if not txn:
return self.notfound()
older = transactions.filter(txn_cls.issued_at <= txn.issued_at)\
.filter(txn_cls.id != txnid)\
.order_by(txn_cls.issued_at.desc())\
.first()
newer = transactions.filter(txn_cls.issued_at >= txn.issued_at)\
.filter(txn_cls.id != txnid)\
.order_by(txn_cls.issued_at)\
.first()
version_diffs = []
for version in self.get_relevant_versions(txn, obj):
diff = self.make_version_diff(version)
version_diffs.append(diff.as_struct())
changed_raw = app.render_datetime(app.localtime(txn.issued_at, from_utc=True))
changed_ago = app.render_time_ago(app.make_utc() - txn.issued_at)
changed_by = str(txn.user)
if self.request.has_perm('users.view'):
changed_by = tags.link_to(changed_by, self.request.route_url('users.view', uuid=txn.user.uuid))
return {
'txnid': txn.id,
'changed': f"{changed_raw} ({changed_ago})",
'changed_by': changed_by,
'remote_addr': txn.remote_addr,
'comment': txn.meta.get('comment'),
'versions': version_diffs,
'url': self.request.route_url(f'{route_prefix}.version', uuid=obj.uuid, txnid=txnid),
'prev_txnid': older.id if older else None,
'next_txnid': newer.id if newer else None,
}
else: # no txnid, return grid data
obj = self.get_instance()
grid = self.make_revisions_grid(obj)
return grid.get_buefy_data()
def view_version(self):
"""
View showing diff details of a particular object version.
@ -4829,10 +4945,10 @@ 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):
def make_version_diff(self, version, *args, **kwargs):
if 'title' not in kwargs:
kwargs['title'] = self.title_for_version(version)
return diffs.VersionDiff(version, old_data, new_data, **kwargs)
return diffs.VersionDiff(version, *args, **kwargs)
##############################
# Configuration Views
@ -5576,6 +5692,16 @@ class MasterView(View):
route_name='{}.version'.format(route_prefix),
permission='{}.versions'.format(permission_prefix))
# revisions data (AJAX)
config.add_route(f'{route_prefix}.revisions_data',
f'{instance_url_prefix}/revisions-data',
request_method='GET')
config.add_view(cls, attr='revisions_data',
route_name=f'{route_prefix}.revisions_data',
permission=f'{permission_prefix}.versions',
renderer='json')
@classmethod
def _defaults_edit_help(cls, config, **kwargs):
route_prefix = cls.get_route_prefix()