Add beginnings of "New Table" feature
nowhere near complete yet, but skeleton is more or less in place
This commit is contained in:
parent
7e852c1836
commit
a061e362c3
|
@ -1135,6 +1135,8 @@ class Form(object):
|
|||
if record:
|
||||
try:
|
||||
return record[field_name]
|
||||
except KeyError:
|
||||
return None
|
||||
except TypeError:
|
||||
return getattr(record, field_name, None)
|
||||
|
||||
|
|
214
tailbone/templates/tables/create.mako
Normal file
214
tailbone/templates/tables/create.mako
Normal file
|
@ -0,0 +1,214 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%inherit file="/master/create.mako" />
|
||||
|
||||
<%def name="extra_styles()">
|
||||
${parent.extra_styles()}
|
||||
<style type="text/css">
|
||||
.label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</%def>
|
||||
|
||||
<%def name="render_buefy_form()">
|
||||
<b-steps v-model="activeStep"
|
||||
:animated="false"
|
||||
rounded
|
||||
:has-navigation="false"
|
||||
vertical
|
||||
icon-pack="fas">
|
||||
|
||||
<b-step-item step="1"
|
||||
value="enter-details"
|
||||
label="Enter Details"
|
||||
clickable>
|
||||
<h3 class="is-size-3 block">
|
||||
Enter Details
|
||||
</h3>
|
||||
${parent.render_buefy_form()}
|
||||
</b-step-item>
|
||||
|
||||
<b-step-item step="2"
|
||||
value="write-model"
|
||||
label="Write Model"
|
||||
clickable>
|
||||
<h3 class="is-size-3 block">
|
||||
Write Model
|
||||
</h3>
|
||||
<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"
|
||||
@click="activeStep = 'enter-details'">
|
||||
Back
|
||||
</b-button>
|
||||
<b-button type="is-primary"
|
||||
icon-pack="fas"
|
||||
icon-left="save"
|
||||
@click="activeStep = 'review-model'">
|
||||
Write model class to file
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</b-step-item>
|
||||
|
||||
<b-step-item step="3"
|
||||
value="review-model"
|
||||
label="Review Model">
|
||||
<h3 class="is-size-3 block">
|
||||
Review Model
|
||||
</h3>
|
||||
<p class="block">TODO: review model class here</p>
|
||||
<div class="buttons">
|
||||
<b-button icon-pack="fas"
|
||||
icon-left="arrow-left"
|
||||
@click="activeStep = 'write-model'">
|
||||
Back
|
||||
</b-button>
|
||||
<b-button type="is-primary"
|
||||
icon-pack="fas"
|
||||
icon-left="check"
|
||||
@click="activeStep = 'write-revision'">
|
||||
Model class looks good!
|
||||
</b-button>
|
||||
</div>
|
||||
</b-step-item>
|
||||
|
||||
<b-step-item step="4"
|
||||
value="write-revision"
|
||||
label="Write Revision">
|
||||
<h3 class="is-size-3 block">
|
||||
Write Revision
|
||||
</h3>
|
||||
<p class="block">
|
||||
You said the model class looked good, so next we will generate
|
||||
a revision script, used to modify DB schema.
|
||||
</p>
|
||||
<p class="block">
|
||||
TODO: write revision script here
|
||||
</p>
|
||||
<div class="buttons">
|
||||
<b-button icon-pack="fas"
|
||||
icon-left="arrow-left"
|
||||
@click="activeStep = 'review-model'">
|
||||
Back
|
||||
</b-button>
|
||||
<b-button type="is-primary"
|
||||
icon-pack="fas"
|
||||
icon-left="save"
|
||||
@click="activeStep = 'review-revision'">
|
||||
Write revision script to file
|
||||
</b-button>
|
||||
</div>
|
||||
</b-step-item>
|
||||
|
||||
<b-step-item step="5"
|
||||
value="review-revision"
|
||||
label="Review Revision">
|
||||
<h3 class="is-size-3 block">
|
||||
Review Revision
|
||||
</h3>
|
||||
<p class="block">TODO: review revision script here</p>
|
||||
<div class="buttons">
|
||||
<b-button icon-pack="fas"
|
||||
icon-left="arrow-left"
|
||||
@click="activeStep = 'write-revision'">
|
||||
Back
|
||||
</b-button>
|
||||
<b-button type="is-primary"
|
||||
icon-pack="fas"
|
||||
icon-left="check"
|
||||
@click="activeStep = 'upgrade-db'">
|
||||
Revision script looks good!
|
||||
</b-button>
|
||||
</div>
|
||||
</b-step-item>
|
||||
|
||||
<b-step-item step="6"
|
||||
value="upgrade-db"
|
||||
label="Upgrade DB">
|
||||
<h3 class="is-size-3 block">
|
||||
Upgrade DB
|
||||
</h3>
|
||||
<p class="block">TODO: upgrade DB here</p>
|
||||
<div class="buttons">
|
||||
<b-button icon-pack="fas"
|
||||
icon-left="arrow-left"
|
||||
@click="activeStep = 'review-revision'">
|
||||
Back
|
||||
</b-button>
|
||||
<b-button type="is-primary"
|
||||
icon-pack="fas"
|
||||
icon-left="check"
|
||||
@click="activeStep = 'review-db'">
|
||||
Upgrade database
|
||||
</b-button>
|
||||
</div>
|
||||
</b-step-item>
|
||||
|
||||
<b-step-item step="7"
|
||||
value="review-db"
|
||||
label="Review DB">
|
||||
<h3 class="is-size-3 block">
|
||||
Review DB
|
||||
</h3>
|
||||
<p class="block">TODO: review DB here</p>
|
||||
<div class="buttons">
|
||||
<b-button icon-pack="fas"
|
||||
icon-left="arrow-left"
|
||||
@click="activeStep = 'upgrade-db'">
|
||||
Back
|
||||
</b-button>
|
||||
<b-button type="is-primary"
|
||||
icon-pack="fas"
|
||||
icon-left="check"
|
||||
@click="activeStep = 'commit-code'">
|
||||
DB looks good!
|
||||
</b-button>
|
||||
</div>
|
||||
</b-step-item>
|
||||
|
||||
<b-step-item step="8"
|
||||
value="commit-code"
|
||||
label="Commit Code">
|
||||
<h3 class="is-size-3 block">
|
||||
Commit Code
|
||||
</h3>
|
||||
<p class="block">TODO: commit changes here</p>
|
||||
<div class="buttons">
|
||||
<b-button icon-pack="fas"
|
||||
icon-left="arrow-left"
|
||||
@click="activeStep = 'review-db'">
|
||||
Back
|
||||
</b-button>
|
||||
<b-button type="is-primary"
|
||||
icon-pack="fas"
|
||||
icon-left="check"
|
||||
@click="alert('TODO: redirect to table view')">
|
||||
Code changes are committed!
|
||||
</b-button>
|
||||
</div>
|
||||
</b-step-item>
|
||||
</b-steps>
|
||||
</%def>
|
||||
|
||||
<%def name="modify_this_page_vars()">
|
||||
${parent.modify_this_page_vars()}
|
||||
<script type="text/javascript">
|
||||
|
||||
ThisPageData.activeStep = null
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
|
||||
${parent.body()}
|
|
@ -676,7 +676,7 @@ class MasterView(View):
|
|||
return self.render_to_response(template, context)
|
||||
|
||||
def make_create_form(self):
|
||||
return self.make_form(self.get_model_class())
|
||||
return self.make_form()
|
||||
|
||||
def save_create_form(self, form):
|
||||
uploads = self.normalize_uploads(form)
|
||||
|
|
|
@ -26,6 +26,12 @@ Views with info about the underlying Rattail tables
|
|||
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import colander
|
||||
from deform import widget as dfwidget
|
||||
|
||||
from tailbone.views import MasterView
|
||||
|
||||
|
||||
|
@ -34,20 +40,31 @@ class TableView(MasterView):
|
|||
Master view for tables
|
||||
"""
|
||||
normalized_model_name = 'table'
|
||||
model_key = 'name'
|
||||
model_key = 'table_name'
|
||||
model_title = "Table"
|
||||
creatable = False
|
||||
editable = False
|
||||
deletable = False
|
||||
viewable = False
|
||||
filterable = False
|
||||
pageable = False
|
||||
|
||||
labels = {
|
||||
'branch_name': "Schema Branch",
|
||||
'model_name': "Model Class",
|
||||
'module_name': "Module",
|
||||
'module_file': "File",
|
||||
}
|
||||
|
||||
grid_columns = [
|
||||
'name',
|
||||
'table_name',
|
||||
'row_count',
|
||||
]
|
||||
|
||||
def __init__(self, request):
|
||||
super(TableView, self).__init__(request)
|
||||
app = self.get_rattail_app()
|
||||
self.db_handler = app.get_db_handler()
|
||||
|
||||
def get_data(self, **kwargs):
|
||||
"""
|
||||
Fetch existing table names and estimate row counts via PG SQL
|
||||
|
@ -62,18 +79,166 @@ class TableView(MasterView):
|
|||
order by n_live_tup desc;
|
||||
"""
|
||||
result = self.Session.execute(sql)
|
||||
return [dict(name=row['relname'], row_count=row['n_live_tup'])
|
||||
return [dict(table_name=row['relname'], row_count=row['n_live_tup'])
|
||||
for row in result]
|
||||
|
||||
def configure_grid(self, g):
|
||||
g.sorters['name'] = g.make_simple_sorter('name', foldcase=True)
|
||||
super(TableView, self).configure_grid(g)
|
||||
|
||||
# table_name
|
||||
g.sorters['table_name'] = g.make_simple_sorter('table_name', foldcase=True)
|
||||
g.set_sort_defaults('table_name')
|
||||
g.set_searchable('table_name')
|
||||
g.set_link('table_name')
|
||||
|
||||
# row_count
|
||||
g.sorters['row_count'] = g.make_simple_sorter('row_count')
|
||||
g.set_sort_defaults('name')
|
||||
|
||||
g.set_searchable('name')
|
||||
def get_instance(self):
|
||||
from sqlalchemy_utils import get_mapper
|
||||
|
||||
# TODO: deprecate / remove this
|
||||
TablesView = TableView
|
||||
model = self.model
|
||||
table_name = self.request.matchdict['table_name']
|
||||
|
||||
sql = """
|
||||
select n_live_tup
|
||||
from pg_stat_user_tables
|
||||
where schemaname = 'public' and relname = :table_name
|
||||
order by n_live_tup desc;
|
||||
"""
|
||||
result = self.Session.execute(sql, {'table_name': table_name})
|
||||
row = result.fetchone()
|
||||
if not row:
|
||||
raise self.notfound()
|
||||
|
||||
data = {
|
||||
'table_name': table_name,
|
||||
'row_count': row['n_live_tup'],
|
||||
}
|
||||
|
||||
table = model.Base.metadata.tables.get(table_name)
|
||||
if table is not None:
|
||||
try:
|
||||
mapper = get_mapper(table)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
data['model_name'] = mapper.class_.__name__
|
||||
data['model_title'] = mapper.class_.get_model_title()
|
||||
data['model_title_plural'] = mapper.class_.get_model_title_plural()
|
||||
data['description'] = mapper.class_.__doc__
|
||||
|
||||
# TODO: how to reliably get branch? must walk all revisions?
|
||||
module_parts = mapper.class_.__module__.split('.')
|
||||
data['branch_name'] = module_parts[0]
|
||||
|
||||
data['module_name'] = mapper.class_.__module__
|
||||
data['module_file'] = sys.modules[mapper.class_.__module__].__file__
|
||||
|
||||
return data
|
||||
|
||||
def get_instance_title(self, table):
|
||||
return table['table_name']
|
||||
|
||||
def make_form_schema(self):
|
||||
return TableSchema()
|
||||
|
||||
def configure_form(self, f):
|
||||
super(TableView, self).configure_form(f)
|
||||
app = self.get_rattail_app()
|
||||
|
||||
# exclude some fields when creating
|
||||
if self.creating:
|
||||
f.remove('row_count',
|
||||
'module_name',
|
||||
'module_file')
|
||||
|
||||
# branch_name
|
||||
if self.creating:
|
||||
|
||||
# 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')
|
||||
|
||||
# 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))
|
||||
|
||||
# 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.")
|
||||
|
||||
# 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'")
|
||||
|
||||
# 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
|
||||
|
||||
@classmethod
|
||||
def defaults(cls, config):
|
||||
rattail_config = config.registry.settings.get('rattail_config')
|
||||
|
||||
# allow creating tables only if *not* production
|
||||
if not rattail_config.production():
|
||||
cls.creatable = True
|
||||
|
||||
cls._defaults(config)
|
||||
|
||||
|
||||
class TablesView(TableView):
|
||||
|
||||
def __init__(self, request):
|
||||
warnings.warn("TablesView is deprecated; please use TableView instead",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
super(TablesView, self).__init__(request)
|
||||
|
||||
|
||||
class TableSchema(colander.Schema):
|
||||
|
||||
table_name = colander.SchemaNode(colander.String())
|
||||
|
||||
row_count = colander.SchemaNode(colander.Integer(),
|
||||
missing=colander.null)
|
||||
|
||||
model_name = colander.SchemaNode(colander.String())
|
||||
|
||||
model_title = colander.SchemaNode(colander.String())
|
||||
|
||||
model_title_plural = colander.SchemaNode(colander.String())
|
||||
|
||||
description = colander.SchemaNode(colander.String())
|
||||
|
||||
branch_name = colander.SchemaNode(colander.String())
|
||||
|
||||
module_name = colander.SchemaNode(colander.String(),
|
||||
missing=colander.null)
|
||||
|
||||
module_file = colander.SchemaNode(colander.String(),
|
||||
missing=colander.null)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
|
|
Loading…
Reference in a new issue