Semi-finish logic for writing new table model class to file

definitely needs more polish and features, but the gist..
This commit is contained in:
Lance Edgar 2023-01-13 03:51:12 -06:00
parent fb7368993c
commit cac005f993
2 changed files with 387 additions and 62 deletions

View file

@ -10,7 +10,7 @@
</style>
</%def>
<%def name="render_buefy_form()">
<%def name="render_this_page()">
<b-steps v-model="activeStep"
:animated="false"
rounded
@ -25,26 +25,257 @@
<h3 class="is-size-3 block">
Enter Details
</h3>
${parent.render_buefy_form()}
<b-field label="Schema Branch" horizontal
message="Leave this set to your custom app branch, unless you know what you're doing.">
<b-select v-model="tableBranch">
<option v-for="branch in branchOptions"
:key="branch"
:value="branch">
{{ branch }}
</option>
</b-select>
</b-field>
<b-field grouped>
<b-field label="Table Name"
message="Should be singular in nature, i.e. 'widget' not 'widgets'">
<b-input v-model="tableName">
</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>
</b-field>
</b-field>
<b-field grouped>
<b-field label="Model Title"
message="Human-friendly singular model title.">
<b-input v-model="tableModelTitle">
</b-input>
</b-field>
<b-field label="Model Title Plural"
message="Human-friendly plural model title.">
<b-input v-model="tableModelTitlePlural">
</b-input>
</b-field>
</b-field>
<b-field label="Description"
message="Brief description of what a record in this table represents.">
<b-input v-model="tableDescription">
</b-input>
</b-field>
<b-field>
<b-checkbox v-model="tableVersioned">
Record version data for this table
</b-checkbox>
</b-field>
<br />
<div class="level-left">
<div class="level-item">
<h4 class="block is-size-4">Columns</h4>
</div>
<div class="level-item">
<b-button type="is-primary"
icon-pack="fas"
icon-left="plus"
@click="tableAddColumn()">
New
</b-button>
</div>
</div>
<b-table
:data="tableColumns">
% if buefy_0_8:
<template slot-scope="props">
% endif
<b-table-column field="name"
label="Name"
% if not buefy_0_8:
v-slot="props"
% endif
>
{{ props.row.name }}
</b-table-column>
<b-table-column field="data_type"
label="Data Type"
% if not buefy_0_8:
v-slot="props"
% endif
>
{{ props.row.data_type }}
</b-table-column>
<b-table-column field="nullable"
label="Nullable"
% if not buefy_0_8:
v-slot="props"
% endif
>
{{ props.row.nullable ? "Yes" : "No" }}
</b-table-column>
<b-table-column field="versioned"
label="Versioned"
:visible="tableVersioned"
% if not buefy_0_8:
v-slot="props"
% endif
>
{{ props.row.versioned ? "Yes" : "No" }}
</b-table-column>
<b-table-column field="description"
label="Description"
% if not buefy_0_8:
v-slot="props"
% endif
>
{{ props.row.description }}
</b-table-column>
<b-table-column field="actions"
label="Actions"
% if not buefy_0_8:
v-slot="props"
% endif
>
<a v-if="props.row.name != 'uuid'"
href="#"
@click.prevent="tableEditColumn(props.row)">
<i class="fas fa-edit"></i>
Edit
</a>
&nbsp;
<a v-if="props.row.name != 'uuid'"
href="#"
class="has-text-danger"
@click.prevent="tableDeleteColumn(props.index)">
<i class="fas fa-trash"></i>
Delete
</a>
&nbsp;
</b-table-column>
% if buefy_0_8:
</template>
% endif
</b-table>
<b-modal has-modal-card
:active.sync="editingColumnShowDialog">
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">
{{ (editingColumn && editingColumn.name) ? "Edit" : "New" }} Column
</p>
</header>
<section class="modal-card-body">
<b-field label="Name">
<b-input v-model="editingColumnName"
ref="editingColumnName">
</b-input>
</b-field>
<b-field label="Data Type">
<b-input v-model="editingColumnDataType"></b-input>
</b-field>
<b-field grouped>
<b-field label="Nullable">
<b-checkbox v-model="editingColumnNullable"
native-value="true">
{{ editingColumnNullable }}
</b-checkbox>
</b-field>
<b-field label="Versioned"
v-if="tableVersioned">
<b-checkbox v-model="editingColumnVersioned"
native-value="true">
{{ editingColumnVersioned }}
</b-checkbox>
</b-field>
</b-field>
<b-field label="Description">
<b-input v-model="editingColumnDescription"></b-input>
</b-field>
</section>
<footer class="modal-card-foot">
<b-button @click="editingColumnShowDialog = false">
Cancel
</b-button>
<b-button type="is-primary"
icon-pack="fas"
icon-left="save"
@click="editingColumnSave()">
Save
</b-button>
</footer>
</div>
</b-modal>
<br />
<div class="buttons">
<b-button type="is-primary"
icon-pack="fas"
icon-left="check"
@click="activeStep = 'write-model'">
Details are complete
</b-button>
</div>
</b-step-item>
<b-step-item step="2"
value="write-model"
label="Write Model"
clickable>
label="Write Model">
<h3 class="is-size-3 block">
Write Model
</h3>
<b-field label="Schema Branch" horizontal>
{{ tableBranch }}
</b-field>
<b-field label="Table Name" horizontal>
{{ tableName }}
</b-field>
<b-field label="Model Class" horizontal>
{{ tableModelName }}
</b-field>
<b-field horizontal label="File">
<b-input v-model="tableModelFile"></b-input>
</b-field>
<div class="form">
<b-field horizontal label="Table Name">
<span>TODO: poser_widget</span>
</b-field>
<b-field horizontal label="Model Class">
<span>TODO: PoserWidget</span>
</b-field>
<b-field horizontal label="File">
<span>TODO: ~/src/poser/poser/db/model/widgets.py</span>
</b-field>
<div class="buttons">
<b-button icon-pack="fas"
icon-left="arrow-left"
@ -54,8 +285,9 @@
<b-button type="is-primary"
icon-pack="fas"
icon-left="save"
@click="activeStep = 'review-model'">
Write model class to file
@click="writeModelFile()"
:disabled="writingModelFile">
{{ writingModelFile ? "Working, please wait..." : "Write model class to file" }}
</b-button>
</div>
</div>
@ -206,6 +438,107 @@
<script type="text/javascript">
ThisPageData.activeStep = null
ThisPageData.branchOptions = ${json.dumps(branch_name_options)|n}
ThisPageData.tableBranch = ${json.dumps(branch_name)|n}
ThisPageData.tableName = '${rattail_app.get_table_prefix()}_widget'
ThisPageData.tableModelName = '${rattail_app.get_class_prefix()}Widget'
ThisPageData.tableModelTitle = 'Widget'
ThisPageData.tableModelTitlePlural = 'Widgets'
ThisPageData.tableDescription = "Represents a cool widget."
ThisPageData.tableVersioned = true
ThisPageData.tableColumns = [{
name: 'uuid',
data_type: 'String(length=32)',
nullable: false,
description: "UUID primary key",
versioned: true,
}]
ThisPageData.editingColumnShowDialog = false
ThisPageData.editingColumn = null
ThisPageData.editingColumnName = null
ThisPageData.editingColumnDataType = null
ThisPageData.editingColumnNullable = true
ThisPageData.editingColumnDescription = null
ThisPageData.editingColumnVersioned = true
ThisPage.methods.tableAddColumn = function() {
this.editingColumn = null
this.editingColumnName = null
this.editingColumnDataType = null
this.editingColumnNullable = true
this.editingColumnDescription = null
this.editingColumnVersioned = true
this.editingColumnShowDialog = true
this.$nextTick(() => {
this.$refs.editingColumnName.focus()
})
}
ThisPage.methods.tableEditColumn = function(column) {
this.editingColumn = column
this.editingColumnName = column.name
this.editingColumnDataType = column.data_type
this.editingColumnNullable = column.nullable
this.editingColumnDescription = column.description
this.editingColumnVersioned = column.versioned
this.editingColumnShowDialog = true
this.$nextTick(() => {
this.$refs.editingColumnName.focus()
})
}
ThisPage.methods.editingColumnSave = function() {
let column
if (this.editingColumn) {
column = this.editingColumn
} else {
column = {}
this.tableColumns.push(column)
}
column.name = this.editingColumnName
column.data_type = this.editingColumnDataType
column.nullable = this.editingColumnNullable
column.description = this.editingColumnDescription
column.versioned = this.editingColumnVersioned
this.editingColumnShowDialog = false
}
ThisPage.methods.tableDeleteColumn = function(index) {
if (confirm("Really delete this column?")) {
this.tableColumns.splice(index, 1)
}
}
ThisPageData.tableModelFile = '${model_dir}widget.py'
ThisPageData.writingModelFile = false
ThisPage.methods.writeModelFile = function() {
this.writingModelFile = true
let url = '${url('{}.write_model_file'.format(route_prefix))}'
let params = {
branch_name: this.tableBranch,
table_name: this.tableName,
model_name: this.tableModelName,
model_title: this.tableModelTitle,
model_title_plural: this.tableModelTitlePlural,
description: this.tableDescription,
versioned: this.tableVersioned,
module_file: this.tableModelFile,
columns: this.tableColumns,
}
this.submitForm(url, params, response => {
this.writingModelFile = false
this.activeStep = 'review-model'
}, response => {
this.writingModelFile = false
})
}
</script>
</%def>

View file

@ -26,6 +26,7 @@ Views with info about the underlying Rattail tables
from __future__ import unicode_literals, absolute_import
import os
import sys
import warnings
@ -160,65 +161,36 @@ class TableView(MasterView):
def make_form_schema(self):
return TableSchema()
def configure_form(self, f):
super(TableView, self).configure_form(f)
def template_kwargs_create(self, **kwargs):
kwargs = super(TableView, self).template_kwargs_create(**kwargs)
app = self.get_rattail_app()
model = self.model
# exclude some fields when creating
if self.creating:
f.remove('row_count',
'module_name',
'module_file')
kwargs['branch_name_options'] = self.db_handler.get_alembic_branch_names()
# branch_name
if self.creating:
branch_name = app.get_table_prefix()
if branch_name not in kwargs['branch_name_options']:
branch_name = None
kwargs['branch_name'] = branch_name
# move this field to top of form, as it's more fundamental
# when creating new table
f.remove('branch_name')
f.insert(0, 'branch_name')
kwargs['model_dir'] = (os.path.dirname(model.__file__)
+ os.sep)
# define options for dropdown
branches = self.db_handler.get_alembic_branch_names()
values = [(branch, branch) for branch in branches]
f.set_widget('branch_name', dfwidget.SelectWidget(values=values))
return kwargs
# default to custom app branch, if applicable
table_prefix = app.get_table_prefix()
if table_prefix in branches:
f.set_default('branch_name', table_prefix)
f.set_helptext('branch_name', "Leave this set to your custom app branch, unless you know what you're doing.")
def write_model_file(self):
data = self.request.json_body
path = data['module_file']
# table_name
if self.creating:
f.set_default('table_name', '{}_widget'.format(app.get_table_prefix()))
f.set_helptext('table_name', "Should be singular in nature, i.e. 'widget' not 'widgets'")
if os.path.exists(path):
return {'error': "File already exists"}
# model_name
if self.creating:
f.set_default('model_name', '{}Widget'.format(app.get_class_prefix()))
f.set_helptext('model_name', "Should be singular in nature, i.e. 'Widget' not 'Widgets'")
# model_title*
if self.creating:
f.set_default('model_title', 'Widget')
f.set_helptext('model_title', "Human-friendly singular model title.")
f.set_default('model_title_plural', 'Widgets')
f.set_helptext('model_title_plural', "Human-friendly plural model title.")
# description
if self.creating:
f.set_default('description', "Represents a cool widget.")
f.set_helptext('description', "Brief description of what a record in this table represents.")
# TODO: not sure yet how to handle "save" action
# def save_create_form(self, form):
# return form.validated
self.db_handler.write_table_model(data, path)
return {'ok': True}
def get_row_data(self, table):
data = []
for i, column in enumerate(table['table'].columns, 1):
data.append({
'column': column,
'sequence': i,
@ -269,8 +241,26 @@ class TableView(MasterView):
if not rattail_config.production():
cls.creatable = True
cls._table_defaults(config)
cls._defaults(config)
@classmethod
def _table_defaults(cls, config):
route_prefix = cls.get_route_prefix()
url_prefix = cls.get_url_prefix()
permission_prefix = cls.get_permission_prefix()
if cls.creatable:
# write model class to file
config.add_route('{}.write_model_file'.format(route_prefix),
'{}/write-model-file'.format(url_prefix),
request_method='POST')
config.add_view(cls, attr='write_model_file',
route_name='{}.write_model_file'.format(route_prefix),
renderer='json',
permission='{}.create'.format(permission_prefix))
class TablesView(TableView):
@ -303,6 +293,8 @@ class TableSchema(colander.Schema):
module_file = colander.SchemaNode(colander.String(),
missing=colander.null)
versioned = colander.SchemaNode(colander.Bool())
def defaults(config, **kwargs):
base = globals()