Add feature to generate new features...
at least that's the idea. guess we'll see where this goes
This commit is contained in:
parent
ce629c91bb
commit
ca602ff845
1
setup.py
1
setup.py
|
@ -85,6 +85,7 @@ requires = [
|
||||||
'ColanderAlchemy', # 0.3.3
|
'ColanderAlchemy', # 0.3.3
|
||||||
'humanize', # 0.5.1
|
'humanize', # 0.5.1
|
||||||
'Mako', # 0.6.2
|
'Mako', # 0.6.2
|
||||||
|
'markdown', # 3.3.3
|
||||||
'openpyxl', # 2.4.7
|
'openpyxl', # 2.4.7
|
||||||
'paginate', # 0.5.6
|
'paginate', # 0.5.6
|
||||||
'paginate_sqlalchemy', # 0.2.0
|
'paginate_sqlalchemy', # 0.2.0
|
||||||
|
|
219
tailbone/templates/generate_feature.mako
Normal file
219
tailbone/templates/generate_feature.mako
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/page.mako" />
|
||||||
|
|
||||||
|
<%def name="title()">Generate Feature</%def>
|
||||||
|
|
||||||
|
<%def name="content_title()"></%def>
|
||||||
|
|
||||||
|
<%def name="extra_styles()">
|
||||||
|
${parent.extra_styles()}
|
||||||
|
<style type="text/css">
|
||||||
|
|
||||||
|
.content.result p {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content.result .codehilite {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="page_content()">
|
||||||
|
|
||||||
|
<b-field horizontal label="App Prefix"
|
||||||
|
message="Unique naming prefix for the app.">
|
||||||
|
<b-input v-model="app.app_prefix"
|
||||||
|
@input="appPrefixChanged">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field horizontal label="App CapPrefix"
|
||||||
|
message="Unique naming prefix for the app, in CapWords style.">
|
||||||
|
<b-input v-model="app.app_cap_prefix"></b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field horizontal label="Feature Type">
|
||||||
|
<b-select v-model="featureType">
|
||||||
|
<option value="new-report">New Report</option>
|
||||||
|
<option value="new-table">New Table</option>
|
||||||
|
</b-select>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<div v-if="featureType == 'new-table'">
|
||||||
|
${h.form(request.current_route_url(), ref='new-table-form')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
${h.hidden('feature_type', value='new-table')}
|
||||||
|
${h.hidden('app_prefix', **{'v-model': 'app.app_prefix'})}
|
||||||
|
${h.hidden('app_cap_prefix', **{'v-model': 'app.app_cap_prefix'})}
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<div class="card">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">New Table</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content">
|
||||||
|
|
||||||
|
<b-field horizontal label="Table Name"
|
||||||
|
:message="`Name for the table within the DB. With prefix this becomes: ${'$'}{app.app_prefix}_${'$'}{new_table.table_name}`">
|
||||||
|
<b-input name="table_name"
|
||||||
|
v-model="new_table.table_name"
|
||||||
|
@input="tableNameChanged">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field horizontal label="Model Name"
|
||||||
|
:message="`Model name for the table, in CapWords style. With prefix this becomes: ${'$'}{app.app_cap_prefix}${'$'}{new_table.model_name}`">
|
||||||
|
<b-input name="model_name" v-model="new_table.model_name"></b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field horizontal label="Model Title"
|
||||||
|
message="Human-friendly singular model title.">
|
||||||
|
<b-input name="model_title" v-model="new_table.model_title"></b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field horizontal label="Plural Model Title"
|
||||||
|
message="Human-friendly plural model title.">
|
||||||
|
<b-input name="model_title_plural" v-model="new_table.model_title_plural"></b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field horizontal label="Description"
|
||||||
|
message="Description of what a record in this table represents.">
|
||||||
|
<b-input name="description" v-model="new_table.description"></b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field horizontal label="Versioned"
|
||||||
|
message="Whether to record version data for this table.">
|
||||||
|
<b-checkbox name="versioned"
|
||||||
|
v-model="new_table.versioned"
|
||||||
|
native-value="true">
|
||||||
|
{{ new_table.versioned }}
|
||||||
|
</b-checkbox>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${h.end_form()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="featureType == 'new-report'">
|
||||||
|
${h.form(request.current_route_url(), ref='new-report-form')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
${h.hidden('feature_type', value='new-report')}
|
||||||
|
${h.hidden('app_prefix', **{'v-model': 'app.app_prefix'})}
|
||||||
|
${h.hidden('app_cap_prefix', **{'v-model': 'app.app_cap_prefix'})}
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<div class="card">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">New Report</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content">
|
||||||
|
|
||||||
|
<b-field horizontal label="Name"
|
||||||
|
message="Human-friendly name for the report.">
|
||||||
|
<b-input name="name" v-model="new_report.name"></b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field horizontal label="Description"
|
||||||
|
message="Description of the report.">
|
||||||
|
<b-input name="description" v-model="new_report.description"></b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${h.end_form()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<div class="buttons" style="padding-left: 8rem;">
|
||||||
|
<once-button type="is-primary"
|
||||||
|
@click="submitFeatureForm()"
|
||||||
|
text="Generate Feature">
|
||||||
|
</once-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card"
|
||||||
|
v-if="resultGenerated">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">
|
||||||
|
<a name="instructions" href="#instructions">Please follow these instructions carefully.</a>
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content result">${rendered_result or ""|n}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="modify_this_page_vars()">
|
||||||
|
${parent.modify_this_page_vars()}
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
ThisPageData.featureType = ${json.dumps(feature_type)|n}
|
||||||
|
ThisPageData.resultGenerated = ${json.dumps(bool(result))|n}
|
||||||
|
|
||||||
|
% if result:
|
||||||
|
ThisPage.mounted = function() {
|
||||||
|
location.href = '#instructions'
|
||||||
|
}
|
||||||
|
% endif
|
||||||
|
|
||||||
|
ThisPageData.app = {
|
||||||
|
<% dform = app_form.make_deform_form() %>
|
||||||
|
% for field in dform:
|
||||||
|
${field.name}: ${app_form.get_vuejs_model_value(field)|n},
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
|
||||||
|
% for key, form in six.iteritems(feature_forms):
|
||||||
|
<% safekey = key.replace('-', '_') %>
|
||||||
|
ThisPageData.${safekey} = {
|
||||||
|
<% dform = feature_forms[key].make_deform_form() %>
|
||||||
|
% for field in dform:
|
||||||
|
${field.name}: ${feature_forms[key].get_vuejs_model_value(field)|n},
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
ThisPage.methods.appPrefixChanged = function(prefix) {
|
||||||
|
let words = prefix.split('_')
|
||||||
|
let capitalized = []
|
||||||
|
words.forEach(word => {
|
||||||
|
capitalized.push(word[0].toUpperCase() + word.substr(1))
|
||||||
|
})
|
||||||
|
|
||||||
|
this.app.app_cap_prefix = capitalized.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
ThisPage.methods.tableNameChanged = function(name) {
|
||||||
|
let words = name.split('_')
|
||||||
|
let capitalized = []
|
||||||
|
words.forEach(word => {
|
||||||
|
capitalized.push(word[0].toUpperCase() + word.substr(1))
|
||||||
|
})
|
||||||
|
|
||||||
|
this.new_table.model_name = capitalized.join('')
|
||||||
|
this.new_table.model_title = capitalized.join(' ')
|
||||||
|
this.new_table.model_title_plural = capitalized.join(' ') + 's'
|
||||||
|
this.new_table.description = `Represents a ${'$'}{this.new_table.model_title}.`
|
||||||
|
}
|
||||||
|
|
||||||
|
ThisPage.methods.submitFeatureForm = function() {
|
||||||
|
let form = this.$refs[this.featureType + '-form']
|
||||||
|
form.submit()
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
${parent.body()}
|
116
tailbone/views/features.py
Normal file
116
tailbone/views/features.py
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Rattail -- Retail Software Framework
|
||||||
|
# Copyright © 2010-2021 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Rattail.
|
||||||
|
#
|
||||||
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
||||||
|
# terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Feature views
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
import six
|
||||||
|
import colander
|
||||||
|
import markdown
|
||||||
|
|
||||||
|
from tailbone import forms
|
||||||
|
from tailbone.views import View
|
||||||
|
|
||||||
|
|
||||||
|
class GenerateFeatureView(View):
|
||||||
|
"""
|
||||||
|
View for generating new feature source code
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, request):
|
||||||
|
super(GenerateFeatureView, self).__init__(request)
|
||||||
|
self.handler = self.get_handler()
|
||||||
|
|
||||||
|
def get_handler(self):
|
||||||
|
app = self.get_rattail_app()
|
||||||
|
handler = app.get_feature_handler()
|
||||||
|
return handler
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
use_buefy = self.get_use_buefy()
|
||||||
|
|
||||||
|
schema = self.handler.make_schema()
|
||||||
|
app_form = forms.Form(schema=schema, request=self.request,
|
||||||
|
use_buefy=use_buefy)
|
||||||
|
for key, value in six.iteritems(self.handler.get_defaults()):
|
||||||
|
app_form.set_default(key, value)
|
||||||
|
|
||||||
|
feature_forms = {}
|
||||||
|
for feature in self.handler.iter_features():
|
||||||
|
schema = feature.make_schema()
|
||||||
|
form = forms.Form(schema=schema, request=self.request,
|
||||||
|
use_buefy=use_buefy)
|
||||||
|
for key, value in six.iteritems(feature.get_defaults()):
|
||||||
|
form.set_default(key, value)
|
||||||
|
feature_forms[feature.feature_key] = form
|
||||||
|
|
||||||
|
result = rendered_result = None
|
||||||
|
feature_type = 'new-table'
|
||||||
|
if self.request.method == 'POST':
|
||||||
|
if app_form.validate(newstyle=True):
|
||||||
|
|
||||||
|
feature_type = self.request.POST['feature_type']
|
||||||
|
feature = self.handler.get_feature(feature_type)
|
||||||
|
if not feature:
|
||||||
|
raise ValueError("Unknown feature type: {}".format(feature_type))
|
||||||
|
|
||||||
|
feature_form = feature_forms[feature.feature_key]
|
||||||
|
if feature_form.validate(newstyle=True):
|
||||||
|
context = dict(app_form.validated)
|
||||||
|
context.update(feature_form.validated)
|
||||||
|
result = self.handler.do_generate(feature, **context)
|
||||||
|
rendered_result = self.render_result(result)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'index_title': "Generate Feature",
|
||||||
|
'handler': self.handler,
|
||||||
|
'use_buefy': use_buefy,
|
||||||
|
'app_form': app_form,
|
||||||
|
'feature_type': feature_type,
|
||||||
|
'feature_forms': feature_forms,
|
||||||
|
'result': result,
|
||||||
|
'rendered_result': rendered_result,
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def render_result(self, result):
|
||||||
|
return markdown.markdown(result, extensions=['fenced_code',
|
||||||
|
'codehilite'])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def defaults(cls, config):
|
||||||
|
|
||||||
|
# generate feature
|
||||||
|
config.add_tailbone_permission('common', 'common.generate_feature',
|
||||||
|
"Generate new feature source code")
|
||||||
|
config.add_route('generate_feature', '/generate-feature')
|
||||||
|
config.add_view(cls, route_name='generate_feature',
|
||||||
|
permission='common.generate_feature',
|
||||||
|
renderer='/generate_feature.mako')
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
GenerateFeatureView.defaults(config)
|
Loading…
Reference in a new issue