Show full version history within the "view" page
avoid full page loads when navigating version history
This commit is contained in:
parent
44112a3a4b
commit
4328b9e385
|
@ -136,6 +136,9 @@ class VersionDiff(Diff):
|
|||
"""
|
||||
|
||||
def __init__(self, version, *args, **kwargs):
|
||||
self.version = version
|
||||
self.mapper = sa.inspect(continuum.parent_class(type(self.version)))
|
||||
self.version_mapper = sa.inspect(type(self.version))
|
||||
self.title = kwargs.pop('title', None)
|
||||
|
||||
if 'nature' not in kwargs:
|
||||
|
@ -146,10 +149,31 @@ class VersionDiff(Diff):
|
|||
else:
|
||||
kwargs['nature'] = 'new'
|
||||
|
||||
if 'fields' not in kwargs:
|
||||
kwargs['fields'] = self.get_default_fields()
|
||||
|
||||
if not args:
|
||||
old_data = {}
|
||||
new_data = {}
|
||||
for field in kwargs['fields']:
|
||||
if version.previous:
|
||||
old_data[field] = getattr(version.previous, field)
|
||||
new_data[field] = getattr(version, field)
|
||||
args = (old_data, new_data)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.version = version
|
||||
self.mapper = sa.inspect(continuum.parent_class(type(self.version)))
|
||||
def get_default_fields(self):
|
||||
fields = sorted(self.version_mapper.columns.keys())
|
||||
|
||||
unwanted = [
|
||||
'transaction_id',
|
||||
'end_transaction_id',
|
||||
'operation_type',
|
||||
]
|
||||
|
||||
return [field for field in fields
|
||||
if field not in unwanted]
|
||||
|
||||
def render_version_value(self, field, value, version):
|
||||
text = HTML.tag('span', c=[repr(value)],
|
||||
|
|
|
@ -1334,6 +1334,7 @@ class Grid(object):
|
|||
context['grid'] = self
|
||||
context['request'] = self.request
|
||||
context.setdefault('allow_save_defaults', True)
|
||||
context.setdefault('view_click_handler', self.get_view_click_handler())
|
||||
return render(template, context)
|
||||
|
||||
def render_buefy(self, template='/grids/buefy.mako', **kwargs):
|
||||
|
@ -1374,6 +1375,10 @@ class Grid(object):
|
|||
context.setdefault('paginated', False)
|
||||
if context['paginated']:
|
||||
context.setdefault('per_page', 20)
|
||||
context['view_click_handler'] = self.get_view_click_handler()
|
||||
return render(template, context)
|
||||
|
||||
def get_view_click_handler(self):
|
||||
|
||||
# locate the 'view' action
|
||||
# TODO: this should be easier, and/or moved elsewhere?
|
||||
|
@ -1388,11 +1393,8 @@ class Grid(object):
|
|||
view = action
|
||||
break
|
||||
|
||||
context['view_click_handler'] = None
|
||||
if view and view.click_handler:
|
||||
context['view_click_handler'] = view.click_handler
|
||||
|
||||
return render(template, context)
|
||||
if view:
|
||||
return view.click_handler
|
||||
|
||||
def set_filters_sequence(self, filters, only=False):
|
||||
"""
|
||||
|
|
|
@ -61,13 +61,14 @@ header .level .theme-picker {
|
|||
display: inline-flex;
|
||||
}
|
||||
|
||||
#content-title {
|
||||
padding: 0.3rem;
|
||||
}
|
||||
|
||||
#content-title h1 {
|
||||
font-size: 2rem;
|
||||
margin-left: 1rem;
|
||||
margin-bottom: 0;
|
||||
margin-right: 1rem;
|
||||
max-width: 50%;
|
||||
overflow: hidden;
|
||||
padding: 0 0.3rem;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/******************************
|
||||
|
|
|
@ -426,17 +426,22 @@
|
|||
|
||||
## Page Title
|
||||
% if capture(self.content_title):
|
||||
<section id="content-title" class="hero is-primary">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<h1 class="title" v-html="contentTitleHTML"></h1>
|
||||
</div>
|
||||
<section id="content-title"
|
||||
class="has-background-primary">
|
||||
<div style="display: flex; align-items: center; padding: 0.5rem;">
|
||||
|
||||
<h1 class="title has-text-white"
|
||||
v-html="contentTitleHTML">
|
||||
</h1>
|
||||
|
||||
<div style="flex-grow: 1; display: flex; gap: 0.5rem;">
|
||||
${self.render_instance_header_title_extras()}
|
||||
</div>
|
||||
<div class="level-right">
|
||||
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
${self.render_instance_header_buttons()}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
% endif
|
||||
|
@ -634,76 +639,60 @@
|
|||
## TODO: is there a better way to check if viewing parent?
|
||||
% if parent_instance is Undefined:
|
||||
% if master.editable and instance_editable and master.has_perm('edit'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('edit', instance)}"
|
||||
icon-left="edit"
|
||||
text="Edit This">
|
||||
</once-button>
|
||||
</div>
|
||||
<once-button tag="a" href="${action_url('edit', instance)}"
|
||||
icon-left="edit"
|
||||
text="Edit This">
|
||||
</once-button>
|
||||
% endif
|
||||
% if master.cloneable and master.has_perm('clone'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('clone', instance)}"
|
||||
icon-left="object-ungroup"
|
||||
text="Clone This">
|
||||
</once-button>
|
||||
</div>
|
||||
<once-button tag="a" href="${action_url('clone', instance)}"
|
||||
icon-left="object-ungroup"
|
||||
text="Clone This">
|
||||
</once-button>
|
||||
% endif
|
||||
% if master.deletable and instance_deletable and master.has_perm('delete'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('delete', instance)}"
|
||||
type="is-danger"
|
||||
icon-left="trash"
|
||||
text="Delete This">
|
||||
</once-button>
|
||||
</div>
|
||||
<once-button tag="a" href="${action_url('delete', instance)}"
|
||||
type="is-danger"
|
||||
icon-left="trash"
|
||||
text="Delete This">
|
||||
</once-button>
|
||||
% endif
|
||||
% else:
|
||||
## viewing row
|
||||
% if instance_deletable and master.has_perm('delete_row'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('delete', instance)}"
|
||||
type="is-danger"
|
||||
icon-left="trash"
|
||||
text="Delete This">
|
||||
</once-button>
|
||||
</div>
|
||||
<once-button tag="a" href="${action_url('delete', instance)}"
|
||||
type="is-danger"
|
||||
icon-left="trash"
|
||||
text="Delete This">
|
||||
</once-button>
|
||||
% endif
|
||||
% endif
|
||||
% elif master and master.editing:
|
||||
% if master.viewable and master.has_perm('view'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('view', instance)}"
|
||||
icon-left="eye"
|
||||
text="View This">
|
||||
</once-button>
|
||||
</div>
|
||||
<once-button tag="a" href="${action_url('view', instance)}"
|
||||
icon-left="eye"
|
||||
text="View This">
|
||||
</once-button>
|
||||
% endif
|
||||
% if master.deletable and instance_deletable and master.has_perm('delete'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('delete', instance)}"
|
||||
type="is-danger"
|
||||
icon-left="trash"
|
||||
text="Delete This">
|
||||
</once-button>
|
||||
</div>
|
||||
<once-button tag="a" href="${action_url('delete', instance)}"
|
||||
type="is-danger"
|
||||
icon-left="trash"
|
||||
text="Delete This">
|
||||
</once-button>
|
||||
% endif
|
||||
% elif master and master.deleting:
|
||||
% if master.viewable and master.has_perm('view'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('view', instance)}"
|
||||
icon-left="eye"
|
||||
text="View This">
|
||||
</once-button>
|
||||
</div>
|
||||
<once-button tag="a" href="${action_url('view', instance)}"
|
||||
icon-left="eye"
|
||||
text="View This">
|
||||
</once-button>
|
||||
% endif
|
||||
% if master.editable and instance_editable and master.has_perm('edit'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('edit', instance)}"
|
||||
icon-left="edit"
|
||||
text="Edit This">
|
||||
</once-button>
|
||||
</div>
|
||||
<once-button tag="a" href="${action_url('edit', instance)}"
|
||||
icon-left="edit"
|
||||
text="Edit This">
|
||||
</once-button>
|
||||
% endif
|
||||
% endif
|
||||
</%def>
|
||||
|
@ -711,40 +700,32 @@
|
|||
<%def name="render_prevnext_header_buttons()">
|
||||
% if show_prev_next is not Undefined and show_prev_next:
|
||||
% if prev_url:
|
||||
<div class="level-item">
|
||||
<b-button tag="a" href="${prev_url}"
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-left">
|
||||
Older
|
||||
</b-button>
|
||||
</div>
|
||||
<b-button tag="a" href="${prev_url}"
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-left">
|
||||
Older
|
||||
</b-button>
|
||||
% else:
|
||||
<div class="level-item">
|
||||
<b-button tag="a" href="#"
|
||||
disabled
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-left">
|
||||
Older
|
||||
</b-button>
|
||||
</div>
|
||||
<b-button tag="a" href="#"
|
||||
disabled
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-left">
|
||||
Older
|
||||
</b-button>
|
||||
% endif
|
||||
% if next_url:
|
||||
<div class="level-item">
|
||||
<b-button tag="a" href="${next_url}"
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-right">
|
||||
Newer
|
||||
</b-button>
|
||||
</div>
|
||||
<b-button tag="a" href="${next_url}"
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-right">
|
||||
Newer
|
||||
</b-button>
|
||||
% else:
|
||||
<div class="level-item">
|
||||
<b-button tag="a" href="#"
|
||||
disabled
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-right">
|
||||
Newer
|
||||
</b-button>
|
||||
</div>
|
||||
<b-button tag="a" href="#"
|
||||
disabled
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-right">
|
||||
Newer
|
||||
</b-button>
|
||||
% endif
|
||||
% endif
|
||||
</%def>
|
||||
|
|
|
@ -254,7 +254,12 @@
|
|||
% if column['field'] in grid.raw_renderers:
|
||||
${grid.raw_renderers[column['field']]()}
|
||||
% elif grid.is_linked(column['field']):
|
||||
<a :href="props.row._action_url_view" v-html="props.row.${column['field']}"></a>
|
||||
<a :href="props.row._action_url_view"
|
||||
% if view_click_handler:
|
||||
@click.prevent="${view_click_handler}"
|
||||
% endif
|
||||
v-html="props.row.${column['field']}">
|
||||
</a>
|
||||
% else:
|
||||
<span v-html="props.row.${column['field']}"></span>
|
||||
% endif
|
||||
|
@ -274,6 +279,9 @@
|
|||
% if action.click_handler:
|
||||
@click.prevent="${action.click_handler}"
|
||||
% endif
|
||||
% if action.target:
|
||||
target="${action.target}"
|
||||
% endif
|
||||
>
|
||||
${action.render_icon()|n}
|
||||
${action.render_label()|n}
|
||||
|
@ -533,7 +541,7 @@
|
|||
## TODO: i noticed buefy docs show using `async` keyword here,
|
||||
## so now i am too. knowing nothing at all of if/how this is
|
||||
## supposed to improve anything. we shall see i guess
|
||||
async loadAsyncData(params, callback) {
|
||||
async loadAsyncData(params, success, failure) {
|
||||
|
||||
if (params === undefined || params === null) {
|
||||
params = new URLSearchParams(this.getBasicParams())
|
||||
|
@ -551,14 +559,17 @@
|
|||
this.lastItem = data.last_item
|
||||
this.loading = false
|
||||
this.checkedRows = this.locateCheckedRows(data.checked_rows)
|
||||
if (callback) {
|
||||
callback()
|
||||
if (success) {
|
||||
success()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.data = []
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
if (failure) {
|
||||
failure()
|
||||
}
|
||||
throw error
|
||||
})
|
||||
},
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%inherit file="/master/form.mako" />
|
||||
|
||||
<%def name="title()">Edit: ${instance_title}</%def>
|
||||
<%def name="title()">${index_title} » ${instance_title} » Edit</%def>
|
||||
|
||||
<%def name="content_title()">Edit: ${instance_title}</%def>
|
||||
|
||||
${parent.body()}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
</%def>
|
||||
|
||||
<%def name="render_instance_header_title_extras()">
|
||||
<span style="width: 2rem;"></span>
|
||||
% if master.touchable and master.has_perm('touch'):
|
||||
<b-button title=""Touch" this record to trigger sync"
|
||||
icon-pack="fas"
|
||||
|
@ -17,6 +16,13 @@
|
|||
:disabled="touchSubmitting">
|
||||
</b-button>
|
||||
% endif
|
||||
% if expose_versions:
|
||||
<b-button icon-pack="fas"
|
||||
icon-left="history"
|
||||
@click="viewingHistory = !viewingHistory">
|
||||
{{ viewingHistory ? "View Current" : "View History" }}
|
||||
</b-button>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="object_helpers()">
|
||||
|
@ -46,9 +52,6 @@
|
|||
## TODO: either make this configurable, or just lose it.
|
||||
## nobody seems to ever find it useful in practice.
|
||||
## <li>${h.link_to("Permalink for this {}".format(model_title), action_url('view', instance))}</li>
|
||||
% if master.has_versions and request.rattail_config.versioning_enabled() and request.has_perm('{}.versions'.format(permission_prefix)):
|
||||
<li>${h.link_to("Version History", action_url('versions', instance))}</li>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="render_row_grid_tools()">
|
||||
|
@ -69,14 +72,152 @@
|
|||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="render_this_page_component()">
|
||||
## TODO: should override this in a cleaner way! too much duplicate code w/ parent template
|
||||
<this-page @change-content-title="changeContentTitle"
|
||||
% if can_edit_help:
|
||||
:configure-fields-help="configureFieldsHelp"
|
||||
% endif
|
||||
% if expose_versions:
|
||||
:viewing-history="viewingHistory"
|
||||
% endif
|
||||
>
|
||||
</this-page>
|
||||
</%def>
|
||||
|
||||
<%def name="render_this_page()">
|
||||
${parent.render_this_page()}
|
||||
% if master.has_rows:
|
||||
<br />
|
||||
% if rows_title:
|
||||
<h4 class="block is-size-4">${rows_title}</h4>
|
||||
% endif
|
||||
${self.render_row_grid_component()}
|
||||
<div
|
||||
% if expose_versions:
|
||||
v-show="!viewingHistory"
|
||||
% endif
|
||||
>
|
||||
|
||||
## render main form
|
||||
${parent.render_this_page()}
|
||||
|
||||
## render row grid
|
||||
% if master.has_rows:
|
||||
<br />
|
||||
% if rows_title:
|
||||
<h4 class="block is-size-4">${rows_title}</h4>
|
||||
% endif
|
||||
${self.render_row_grid_component()}
|
||||
% endif
|
||||
</div>
|
||||
|
||||
% if expose_versions:
|
||||
<div v-show="viewingHistory">
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 2rem;">
|
||||
<h3 class="is-size-3">Version History</h3>
|
||||
<p class="block">
|
||||
<a href="${master.get_action_url('versions', instance)}"
|
||||
target="_blank">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
View as separate page
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<versions-grid ref="versionsGrid"
|
||||
@view-revision="viewRevision">
|
||||
</versions-grid>
|
||||
|
||||
<b-modal :active.sync="viewVersionShowDialog" :width="1200">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div style="display: flex; flex-direction: column; gap: 1.5rem;">
|
||||
|
||||
<div style="display: flex; gap: 1rem;">
|
||||
|
||||
<div style="flex-grow: 1;">
|
||||
<b-field horizontal label="Changed">
|
||||
<div v-html="viewVersionData.changed"></div>
|
||||
</b-field>
|
||||
<b-field horizontal label="Changed by">
|
||||
<div v-html="viewVersionData.changed_by"></div>
|
||||
</b-field>
|
||||
<b-field horizontal label="IP Address">
|
||||
<div v-html="viewVersionData.remote_addr"></div>
|
||||
</b-field>
|
||||
<b-field horizontal label="Comment">
|
||||
<div v-html="viewVersionData.comment"></div>
|
||||
</b-field>
|
||||
<b-field horizontal label="TXN ID">
|
||||
<div v-html="viewVersionData.txnid"></div>
|
||||
</b-field>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; justify-content: space-between;">
|
||||
|
||||
<div class="buttons">
|
||||
<b-button @click="viewPrevRevision()"
|
||||
type="is-primary"
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-left"
|
||||
:disabled="!viewVersionData.prev_txnid">
|
||||
Older
|
||||
</b-button>
|
||||
<b-button @click="viewNextRevision()"
|
||||
type="is-primary"
|
||||
icon-pack="fas"
|
||||
icon-right="arrow-right"
|
||||
:disabled="!viewVersionData.next_txnid">
|
||||
Newer
|
||||
</b-button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a :href="viewVersionData.url"
|
||||
target="_blank">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
View as separate page
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<b-button @click="toggleVersionFields()">
|
||||
{{ viewVersionShowAllFields ? "Show Diffs Only" : "Show All Fields" }}
|
||||
</b-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-for="version in viewVersionData.versions"
|
||||
:key="version.key">
|
||||
|
||||
<p class="block has-text-weight-bold">
|
||||
{{ version.model_title }}
|
||||
</p>
|
||||
|
||||
<table class="diff monospace is-size-7"
|
||||
:class="version.diff_class">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>field name</th>
|
||||
<th>old value</th>
|
||||
<th>new value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="field in version.fields"
|
||||
:key="field"
|
||||
:class="{diff: version.values[field].after != version.values[field].before}"
|
||||
v-show="viewVersionShowAllFields || version.values[field].after != version.values[field].before">
|
||||
<td class="field has-text-weight-bold">{{ field }}</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>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<b-loading :active.sync="viewVersionLoading" :is-full-page="false"></b-loading>
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</div>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
|
@ -90,12 +231,79 @@
|
|||
${rows_grid.render_buefy(allow_save_defaults=False, tools=capture(self.render_row_grid_tools))|n}
|
||||
% endif
|
||||
${parent.render_this_page_template()}
|
||||
% if expose_versions:
|
||||
${versions_grid.render_buefy()|n}
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="modify_this_page_vars()">
|
||||
${parent.modify_this_page_vars()}
|
||||
% if expose_versions:
|
||||
<script type="text/javascript">
|
||||
|
||||
ThisPage.props.viewingHistory = Boolean
|
||||
|
||||
ThisPageData.gettingRevisions = false
|
||||
ThisPageData.gotRevisions = false
|
||||
|
||||
ThisPageData.viewVersionShowDialog = false
|
||||
ThisPageData.viewVersionData = {}
|
||||
ThisPageData.viewVersionShowAllFields = false
|
||||
ThisPageData.viewVersionLoading = false
|
||||
|
||||
// auto-fetch grid results when first viewing history
|
||||
ThisPage.watch.viewingHistory = function(newval, oldval) {
|
||||
if (!this.gotRevisions && !this.gettingRevisions) {
|
||||
this.gettingRevisions = true
|
||||
this.$refs.versionsGrid.loadAsyncData(null, () => {
|
||||
this.gettingRevisions = false
|
||||
this.gotRevisions = true
|
||||
}, () => {
|
||||
this.gettingRevisions = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
VersionsGrid.methods.viewRevision = function(row) {
|
||||
this.$emit('view-revision', row)
|
||||
}
|
||||
|
||||
ThisPage.methods.viewRevision = function(row) {
|
||||
this.viewVersionLoading = true
|
||||
|
||||
let url = '${master.get_action_url('revisions_data', instance)}'
|
||||
let params = {txnid: row.id}
|
||||
this.simpleGET(url, params, response => {
|
||||
this.viewVersionData = response.data
|
||||
this.viewVersionLoading = false
|
||||
}, response => {
|
||||
this.viewVersionLoading = false
|
||||
})
|
||||
|
||||
this.viewVersionShowDialog = true
|
||||
}
|
||||
|
||||
ThisPage.methods.viewPrevRevision = function() {
|
||||
this.viewRevision({id: this.viewVersionData.prev_txnid})
|
||||
}
|
||||
|
||||
ThisPage.methods.viewNextRevision = function() {
|
||||
this.viewRevision({id: this.viewVersionData.next_txnid})
|
||||
}
|
||||
|
||||
ThisPage.methods.toggleVersionFields = function() {
|
||||
this.viewVersionShowAllFields = !this.viewVersionShowAllFields
|
||||
}
|
||||
|
||||
</script>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="modify_whole_page_vars()">
|
||||
${parent.modify_whole_page_vars()}
|
||||
% if master.touchable and master.has_perm('touch'):
|
||||
<script type="text/javascript">
|
||||
<script type="text/javascript">
|
||||
|
||||
% if master.touchable and master.has_perm('touch'):
|
||||
|
||||
WholePageData.touchSubmitting = false
|
||||
|
||||
|
@ -104,21 +312,30 @@
|
|||
location.href = '${master.get_action_url('touch', instance)}'
|
||||
}
|
||||
|
||||
</script>
|
||||
% endif
|
||||
% endif
|
||||
|
||||
% if expose_versions:
|
||||
WholePageData.viewingHistory = false
|
||||
% endif
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="finalize_this_page_vars()">
|
||||
${parent.finalize_this_page_vars()}
|
||||
% if master.has_rows:
|
||||
<script type="text/javascript">
|
||||
|
||||
TailboneGrid.data = function() { return TailboneGridData }
|
||||
% if master.has_rows:
|
||||
TailboneGrid.data = function() { return TailboneGridData }
|
||||
Vue.component('tailbone-grid', TailboneGrid)
|
||||
% endif
|
||||
|
||||
Vue.component('tailbone-grid', TailboneGrid)
|
||||
% if expose_versions:
|
||||
VersionsGrid.data = function() { return VersionsGridData }
|
||||
Vue.component('versions-grid', VersionsGrid)
|
||||
% endif
|
||||
|
||||
</script>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
|
||||
|
|
|
@ -45,13 +45,18 @@
|
|||
<div class="field">${transaction.meta.get('comment') or ''}</div>
|
||||
</div>
|
||||
|
||||
<div class="field-wrapper">
|
||||
<label>TXN ID</label>
|
||||
<div class="field">${transaction.id}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div><!-- form-wrapper -->
|
||||
|
||||
<div class="versions-wrapper">
|
||||
% for diff in version_diffs:
|
||||
<h2>${diff.title}</h2>
|
||||
<h4 class="is-size-4 block">${diff.title}</h4>
|
||||
${diff.render_html()}
|
||||
% endfor
|
||||
</div>
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue