+
+
+
+ Enter Details
+
+ ${parent.render_buefy_form()}
+
+
+
+
+ Write Model
+
+
+
+
+
+
+ Review Model
+
+ TODO: review model class here
+
+
+ Back
+
+
+ Model class looks good!
+
+
+
+
+
+
+ Write Revision
+
+
+ You said the model class looked good, so next we will generate
+ a revision script, used to modify DB schema.
+
+
+ TODO: write revision script here
+
+
+
+ Back
+
+
+ Write revision script to file
+
+
+
+
+
+
+ Review Revision
+
+ TODO: review revision script here
+
+
+ Back
+
+
+ Revision script looks good!
+
+
+
+
+
+
+ Upgrade DB
+
+ TODO: upgrade DB here
+
+
+ Back
+
+
+ Upgrade database
+
+
+
+
+
+
+ Review DB
+
+ TODO: review DB here
+
+
+ Back
+
+
+ DB looks good!
+
+
+
+
+
+
+ Commit Code
+
+ TODO: commit changes here
+
+
+ Back
+
+
+ Code changes are committed!
+
+
+
+
+%def>
+
+<%def name="modify_this_page_vars()">
+ ${parent.modify_this_page_vars()}
+
+%def>
+
+
+${parent.body()}
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index d382918c..98355ec3 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -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)
diff --git a/tailbone/views/tables.py b/tailbone/views/tables.py
index 5d4f7d95..49d9e7a5 100644
--- a/tailbone/views/tables.py
+++ b/tailbone/views/tables.py
@@ -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):