Warn user if DB not up to date, in new table wizard

also start adding 'dirty' page behavior, to warn user if navigating
away that changes will be lost

also improve steps in wizard, so page header is scrolled into view
when prev/next buttons are clicked.  unfortunately it still does not
work right if user clicks the step number on left of screen..
This commit is contained in:
Lance Edgar 2023-05-12 21:27:15 -05:00
parent f5f973dc3a
commit 29817653ed
4 changed files with 115 additions and 22 deletions

View file

@ -11,8 +11,25 @@
</%def>
<%def name="render_this_page()">
## scroll target used when navigating prev/next
<div ref="showme"></div>
% if not alembic_current_head:
<b-notification type="is-warning"
:closable="false">
<p class="block">
DB is not up to date! There are
${h.link_to("pending migrations", url('{}.migrations'.format(route_prefix)))}.
</p>
<p class="block">
(This will be a problem if you wish to auto-generate a migration for a new table.)
</p>
</b-notification>
% endif
<b-steps v-model="activeStep"
:animated="false"
animated
rounded
:has-navigation="false"
vertical
@ -28,7 +45,8 @@
<b-field label="Schema Branch"
message="Leave this set to your custom app branch, unless you know what you're doing.">
<b-select v-model="alembicBranch">
<b-select v-model="alembicBranch"
@input="dirty = true">
<option v-for="branch in alembicBranchOptions"
:key="branch"
:value="branch">
@ -41,13 +59,15 @@
<b-field label="Table Name"
message="Should be singular in nature, i.e. 'widget' not 'widgets'">
<b-input v-model="tableName">
<b-input v-model="tableName"
@input="dirty = true">
</b-input>
</b-field>
<b-field label="Model/Class Name"
message="Should be singular in nature, i.e. 'Widget' not 'Widgets'">
<b-input v-model="tableModelName">
<b-input v-model="tableModelName"
@input="dirty = true">
</b-input>
</b-field>
@ -57,13 +77,15 @@
<b-field label="Model Title"
message="Human-friendly singular model title.">
<b-input v-model="tableModelTitle">
<b-input v-model="tableModelTitle"
@input="dirty = true">
</b-input>
</b-field>
<b-field label="Model Title Plural"
message="Human-friendly plural model title.">
<b-input v-model="tableModelTitlePlural">
<b-input v-model="tableModelTitlePlural"
@input="dirty = true">
</b-input>
</b-field>
@ -71,12 +93,14 @@
<b-field label="Description"
message="Brief description of what a record in this table represents.">
<b-input v-model="tableDescription">
<b-input v-model="tableDescription"
@input="dirty = true">
</b-input>
</b-field>
<b-field>
<b-checkbox v-model="tableVersioned">
<b-checkbox v-model="tableVersioned"
@input="dirty = true">
Record version data for this table
</b-checkbox>
</b-field>
@ -285,7 +309,7 @@
<b-button type="is-primary"
icon-pack="fas"
icon-left="check"
@click="activeStep = 'write-model'">
@click="showStep('write-model')">
Details are complete
</b-button>
</div>
@ -325,7 +349,7 @@
<div class="buttons">
<b-button icon-pack="fas"
icon-left="arrow-left"
@click="activeStep = 'enter-details'">
@click="showStep('enter-details')">
Back
</b-button>
<b-button type="is-primary"
@ -337,7 +361,7 @@
</b-button>
<b-button icon-pack="fas"
icon-left="arrow-right"
@click="activeStep = 'review-model'">
@click="showStep('review-model')">
Skip
</b-button>
</div>
@ -435,19 +459,19 @@
<div class="buttons">
<b-button icon-pack="fas"
icon-left="arrow-left"
@click="activeStep = 'write-model'">
@click="showStep('write-model')">
Back
</b-button>
<b-button type="is-primary"
icon-pack="fas"
icon-left="check"
@click="activeStep = 'write-revision'"
@click="showStep('write-revision')"
:disabled="!modelImported">
Model class looks good!
</b-button>
<b-button icon-pack="fas"
icon-left="arrow-right"
@click="activeStep = 'write-revision'">
@click="showStep('write-revision')">
Skip
</b-button>
</div>
@ -486,7 +510,7 @@
<div class="buttons">
<b-button icon-pack="fas"
icon-left="arrow-left"
@click="activeStep = 'review-model'">
@click="showStep('review-model')">
Back
</b-button>
<b-button type="is-primary"
@ -498,7 +522,7 @@
</b-button>
<b-button icon-pack="fas"
icon-left="arrow-right"
@click="activeStep = 'review-revision'">
@click="showStep('review-revision')">
Skip
</b-button>
</div>
@ -526,13 +550,13 @@
<div class="buttons">
<b-button icon-pack="fas"
icon-left="arrow-left"
@click="activeStep = 'write-revision'">
@click="showStep('write-revision')">
Back
</b-button>
<b-button type="is-primary"
icon-pack="fas"
icon-left="check"
@click="activeStep = 'upgrade-db'">
@click="showStep('upgrade-db')">
Revision script looks good!
</b-button>
</div>
@ -553,7 +577,7 @@
<div class="buttons">
<b-button icon-pack="fas"
icon-left="arrow-left"
@click="activeStep = 'review-revision'">
@click="showStep('review-revision')">
Back
</b-button>
<b-button type="is-primary"
@ -627,13 +651,13 @@
<div class="buttons">
<b-button icon-pack="fas"
icon-left="arrow-left"
@click="activeStep = 'upgrade-db'">
@click="showStep('upgrade-db')">
Back
</b-button>
<b-button type="is-primary"
icon-pack="fas"
icon-left="check"
@click="activeStep = 'commit-code'"
@click="showStep('commit-code')"
:disabled="!tableCheckAttempted || tableCheckProblem">
DB looks good!
</b-button>
@ -658,7 +682,7 @@
<div class="buttons">
<b-button icon-pack="fas"
icon-left="arrow-left"
@click="activeStep = 'review-db'">
@click="showStep('review-db')">
Back
</b-button>
<once-button type="is-primary"
@ -675,6 +699,9 @@
${parent.modify_this_page_vars()}
<script type="text/javascript">
// nb. for warning user they may lose changes if leaving page
ThisPageData.dirty = false
ThisPageData.activeStep = null
ThisPageData.alembicBranchOptions = ${json.dumps(branch_name_options)|n}
@ -713,6 +740,15 @@
ThisPageData.editingColumnVersioned = true
ThisPageData.editingColumnRelationship = null
ThisPage.methods.showStep = function(step) {
this.activeStep = step
// scroll so top of page is shown
this.$nextTick(() => {
this.$refs['showme'].scrollIntoView(true)
})
}
ThisPage.methods.tableAddColumn = function() {
this.editingColumn = null
this.editingColumnName = null
@ -801,12 +837,14 @@
column.versioned = this.editingColumnVersioned
column.relationship = this.editingColumnRelationship
this.dirty = true
this.editingColumnShowDialog = false
}
ThisPage.methods.tableDeleteColumn = function(index) {
if (confirm("Really delete this column?")) {
this.tableColumns.splice(index, 1)
this.dirty = true
}
}
@ -929,6 +967,20 @@
})
}
// cf. https://stackoverflow.com/a/56551646
ThisPage.methods.beforeWindowUnload = function(e) {
// warn user if navigating away would lose changes
if (this.dirty) {
e.preventDefault()
e.returnValue = ''
}
}
ThisPage.created = function() {
window.addEventListener('beforeunload', this.beforeWindowUnload)
}
</script>
</%def>

View file

@ -0,0 +1,12 @@
## -*- coding: utf-8; -*-
<%inherit file="/master/index.mako" />
<%def name="context_menu_items()">
${parent.context_menu_items()}
% if master.has_perm('migrations'):
<li>${h.link_to("View / Apply Migrations", url('{}.migrations'.format(route_prefix)))}</li>
% endif
</%def>
${parent.body()}

View file

@ -0,0 +1,11 @@
## -*- coding: utf-8; -*-
<%inherit file="/page.mako" />
<%def name="title()">Schema Migrations</%def>
<%def name="render_this_page()">
<h3 class="is-size-3">TODO: show current revisions and allow DB upgrades</h3>
</%def>
${parent.body()}

View file

@ -194,6 +194,8 @@ class TableView(MasterView):
app = self.get_rattail_app()
model = self.model
kwargs['alembic_current_head'] = self.db_handler.check_alembic_current_head()
kwargs['branch_name_options'] = self.db_handler.get_alembic_branch_names()
branch_name = app.get_table_prefix()
@ -331,6 +333,11 @@ class TableView(MasterView):
return HTML.tag('span', title=text, c="{} ...".format(text[:max_length]))
def migrations(self):
# TODO: allow alembic upgrade on POST
# TODO: pass current revisions to page context
return self.render_to_response('migrations', {})
@classmethod
def defaults(cls, config):
rattail_config = config.registry.settings.get('rattail_config')
@ -348,6 +355,17 @@ class TableView(MasterView):
url_prefix = cls.get_url_prefix()
permission_prefix = cls.get_permission_prefix()
# migrations
config.add_tailbone_permission(permission_prefix,
'{}.migrations'.format(permission_prefix),
"View / apply Alembic migrations")
config.add_route('{}.migrations'.format(route_prefix),
'{}/migrations'.format(url_prefix))
config.add_view(cls, attr='migrations',
route_name='{}.migrations'.format(route_prefix),
renderer='json',
permission='{}.migrations'.format(permission_prefix))
if cls.creatable:
# write model class to file