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
					
				
					 4 changed files with 391 additions and 10 deletions
				
			
		|  | @ -1135,6 +1135,8 @@ class Form(object): | ||||||
|         if record: |         if record: | ||||||
|             try: |             try: | ||||||
|                 return record[field_name] |                 return record[field_name] | ||||||
|  |             except KeyError: | ||||||
|  |                 return None | ||||||
|             except TypeError: |             except TypeError: | ||||||
|                 return getattr(record, field_name, None) |                 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) |         return self.render_to_response(template, context) | ||||||
| 
 | 
 | ||||||
|     def make_create_form(self): |     def make_create_form(self): | ||||||
|         return self.make_form(self.get_model_class()) |         return self.make_form() | ||||||
| 
 | 
 | ||||||
|     def save_create_form(self, form): |     def save_create_form(self, form): | ||||||
|         uploads = self.normalize_uploads(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 | from __future__ import unicode_literals, absolute_import | ||||||
| 
 | 
 | ||||||
|  | import sys | ||||||
|  | import warnings | ||||||
|  | 
 | ||||||
|  | import colander | ||||||
|  | from deform import widget as dfwidget | ||||||
|  | 
 | ||||||
| from tailbone.views import MasterView | from tailbone.views import MasterView | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -34,20 +40,31 @@ class TableView(MasterView): | ||||||
|     Master view for tables |     Master view for tables | ||||||
|     """ |     """ | ||||||
|     normalized_model_name = 'table' |     normalized_model_name = 'table' | ||||||
|     model_key = 'name' |     model_key = 'table_name' | ||||||
|     model_title = "Table" |     model_title = "Table" | ||||||
|     creatable = False |     creatable = False | ||||||
|     editable = False |     editable = False | ||||||
|     deletable = False |     deletable = False | ||||||
|     viewable = False |  | ||||||
|     filterable = False |     filterable = False | ||||||
|     pageable = False |     pageable = False | ||||||
| 
 | 
 | ||||||
|  |     labels = { | ||||||
|  |         'branch_name': "Schema Branch", | ||||||
|  |         'model_name': "Model Class", | ||||||
|  |         'module_name': "Module", | ||||||
|  |         'module_file': "File", | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     grid_columns = [ |     grid_columns = [ | ||||||
|         'name', |         'table_name', | ||||||
|         'row_count', |         '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): |     def get_data(self, **kwargs): | ||||||
|         """ |         """ | ||||||
|         Fetch existing table names and estimate row counts via PG SQL |         Fetch existing table names and estimate row counts via PG SQL | ||||||
|  | @ -62,18 +79,166 @@ class TableView(MasterView): | ||||||
|         order by n_live_tup desc; |         order by n_live_tup desc; | ||||||
|         """ |         """ | ||||||
|         result = self.Session.execute(sql) |         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] |                 for row in result] | ||||||
| 
 | 
 | ||||||
|     def configure_grid(self, g): |     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.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 |         model = self.model | ||||||
| TablesView = TableView |         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): | def defaults(config, **kwargs): | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar