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>
<%def name="render_this_page()"> <%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" <b-steps v-model="activeStep"
:animated="false" animated
rounded rounded
:has-navigation="false" :has-navigation="false"
vertical vertical
@ -28,7 +45,8 @@
<b-field label="Schema Branch" <b-field label="Schema Branch"
message="Leave this set to your custom app branch, unless you know what you're doing."> 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" <option v-for="branch in alembicBranchOptions"
:key="branch" :key="branch"
:value="branch"> :value="branch">
@ -41,13 +59,15 @@
<b-field label="Table Name" <b-field label="Table Name"
message="Should be singular in nature, i.e. 'widget' not 'widgets'"> 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-input>
</b-field> </b-field>
<b-field label="Model/Class Name" <b-field label="Model/Class Name"
message="Should be singular in nature, i.e. 'Widget' not 'Widgets'"> 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-input>
</b-field> </b-field>
@ -57,13 +77,15 @@
<b-field label="Model Title" <b-field label="Model Title"
message="Human-friendly singular model title."> message="Human-friendly singular model title.">
<b-input v-model="tableModelTitle"> <b-input v-model="tableModelTitle"
@input="dirty = true">
</b-input> </b-input>
</b-field> </b-field>
<b-field label="Model Title Plural" <b-field label="Model Title Plural"
message="Human-friendly plural model title."> message="Human-friendly plural model title.">
<b-input v-model="tableModelTitlePlural"> <b-input v-model="tableModelTitlePlural"
@input="dirty = true">
</b-input> </b-input>
</b-field> </b-field>
@ -71,12 +93,14 @@
<b-field label="Description" <b-field label="Description"
message="Brief description of what a record in this table represents."> 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-input>
</b-field> </b-field>
<b-field> <b-field>
<b-checkbox v-model="tableVersioned"> <b-checkbox v-model="tableVersioned"
@input="dirty = true">
Record version data for this table Record version data for this table
</b-checkbox> </b-checkbox>
</b-field> </b-field>
@ -285,7 +309,7 @@
<b-button type="is-primary" <b-button type="is-primary"
icon-pack="fas" icon-pack="fas"
icon-left="check" icon-left="check"
@click="activeStep = 'write-model'"> @click="showStep('write-model')">
Details are complete Details are complete
</b-button> </b-button>
</div> </div>
@ -325,7 +349,7 @@
<div class="buttons"> <div class="buttons">
<b-button icon-pack="fas" <b-button icon-pack="fas"
icon-left="arrow-left" icon-left="arrow-left"
@click="activeStep = 'enter-details'"> @click="showStep('enter-details')">
Back Back
</b-button> </b-button>
<b-button type="is-primary" <b-button type="is-primary"
@ -337,7 +361,7 @@
</b-button> </b-button>
<b-button icon-pack="fas" <b-button icon-pack="fas"
icon-left="arrow-right" icon-left="arrow-right"
@click="activeStep = 'review-model'"> @click="showStep('review-model')">
Skip Skip
</b-button> </b-button>
</div> </div>
@ -435,19 +459,19 @@
<div class="buttons"> <div class="buttons">
<b-button icon-pack="fas" <b-button icon-pack="fas"
icon-left="arrow-left" icon-left="arrow-left"
@click="activeStep = 'write-model'"> @click="showStep('write-model')">
Back Back
</b-button> </b-button>
<b-button type="is-primary" <b-button type="is-primary"
icon-pack="fas" icon-pack="fas"
icon-left="check" icon-left="check"
@click="activeStep = 'write-revision'" @click="showStep('write-revision')"
:disabled="!modelImported"> :disabled="!modelImported">
Model class looks good! Model class looks good!
</b-button> </b-button>
<b-button icon-pack="fas" <b-button icon-pack="fas"
icon-left="arrow-right" icon-left="arrow-right"
@click="activeStep = 'write-revision'"> @click="showStep('write-revision')">
Skip Skip
</b-button> </b-button>
</div> </div>
@ -486,7 +510,7 @@
<div class="buttons"> <div class="buttons">
<b-button icon-pack="fas" <b-button icon-pack="fas"
icon-left="arrow-left" icon-left="arrow-left"
@click="activeStep = 'review-model'"> @click="showStep('review-model')">
Back Back
</b-button> </b-button>
<b-button type="is-primary" <b-button type="is-primary"
@ -498,7 +522,7 @@
</b-button> </b-button>
<b-button icon-pack="fas" <b-button icon-pack="fas"
icon-left="arrow-right" icon-left="arrow-right"
@click="activeStep = 'review-revision'"> @click="showStep('review-revision')">
Skip Skip
</b-button> </b-button>
</div> </div>
@ -526,13 +550,13 @@
<div class="buttons"> <div class="buttons">
<b-button icon-pack="fas" <b-button icon-pack="fas"
icon-left="arrow-left" icon-left="arrow-left"
@click="activeStep = 'write-revision'"> @click="showStep('write-revision')">
Back Back
</b-button> </b-button>
<b-button type="is-primary" <b-button type="is-primary"
icon-pack="fas" icon-pack="fas"
icon-left="check" icon-left="check"
@click="activeStep = 'upgrade-db'"> @click="showStep('upgrade-db')">
Revision script looks good! Revision script looks good!
</b-button> </b-button>
</div> </div>
@ -553,7 +577,7 @@
<div class="buttons"> <div class="buttons">
<b-button icon-pack="fas" <b-button icon-pack="fas"
icon-left="arrow-left" icon-left="arrow-left"
@click="activeStep = 'review-revision'"> @click="showStep('review-revision')">
Back Back
</b-button> </b-button>
<b-button type="is-primary" <b-button type="is-primary"
@ -627,13 +651,13 @@
<div class="buttons"> <div class="buttons">
<b-button icon-pack="fas" <b-button icon-pack="fas"
icon-left="arrow-left" icon-left="arrow-left"
@click="activeStep = 'upgrade-db'"> @click="showStep('upgrade-db')">
Back Back
</b-button> </b-button>
<b-button type="is-primary" <b-button type="is-primary"
icon-pack="fas" icon-pack="fas"
icon-left="check" icon-left="check"
@click="activeStep = 'commit-code'" @click="showStep('commit-code')"
:disabled="!tableCheckAttempted || tableCheckProblem"> :disabled="!tableCheckAttempted || tableCheckProblem">
DB looks good! DB looks good!
</b-button> </b-button>
@ -658,7 +682,7 @@
<div class="buttons"> <div class="buttons">
<b-button icon-pack="fas" <b-button icon-pack="fas"
icon-left="arrow-left" icon-left="arrow-left"
@click="activeStep = 'review-db'"> @click="showStep('review-db')">
Back Back
</b-button> </b-button>
<once-button type="is-primary" <once-button type="is-primary"
@ -675,6 +699,9 @@
${parent.modify_this_page_vars()} ${parent.modify_this_page_vars()}
<script type="text/javascript"> <script type="text/javascript">
// nb. for warning user they may lose changes if leaving page
ThisPageData.dirty = false
ThisPageData.activeStep = null ThisPageData.activeStep = null
ThisPageData.alembicBranchOptions = ${json.dumps(branch_name_options)|n} ThisPageData.alembicBranchOptions = ${json.dumps(branch_name_options)|n}
@ -713,6 +740,15 @@
ThisPageData.editingColumnVersioned = true ThisPageData.editingColumnVersioned = true
ThisPageData.editingColumnRelationship = null 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() { ThisPage.methods.tableAddColumn = function() {
this.editingColumn = null this.editingColumn = null
this.editingColumnName = null this.editingColumnName = null
@ -801,12 +837,14 @@
column.versioned = this.editingColumnVersioned column.versioned = this.editingColumnVersioned
column.relationship = this.editingColumnRelationship column.relationship = this.editingColumnRelationship
this.dirty = true
this.editingColumnShowDialog = false this.editingColumnShowDialog = false
} }
ThisPage.methods.tableDeleteColumn = function(index) { ThisPage.methods.tableDeleteColumn = function(index) {
if (confirm("Really delete this column?")) { if (confirm("Really delete this column?")) {
this.tableColumns.splice(index, 1) 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> </script>
</%def> </%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() app = self.get_rattail_app()
model = self.model 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() kwargs['branch_name_options'] = self.db_handler.get_alembic_branch_names()
branch_name = app.get_table_prefix() branch_name = app.get_table_prefix()
@ -331,6 +333,11 @@ class TableView(MasterView):
return HTML.tag('span', title=text, c="{} ...".format(text[:max_length])) 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 @classmethod
def defaults(cls, config): def defaults(cls, config):
rattail_config = config.registry.settings.get('rattail_config') rattail_config = config.registry.settings.get('rattail_config')
@ -348,6 +355,17 @@ class TableView(MasterView):
url_prefix = cls.get_url_prefix() url_prefix = cls.get_url_prefix()
permission_prefix = cls.get_permission_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: if cls.creatable:
# write model class to file # write model class to file