Massive overhaul of "generate project" feature
previous incarnation was woefully lacking. new feature is much more extensible. still need to remove old POS integration specifics in some places. and a couple of unrelated things that snuck in.. - deprecate `rattail.util.OrderedDict` - deprecate `rattail.util.import_module_path()` - deprecate `rattail.util.import_reload()`
This commit is contained in:
parent
026d98551c
commit
2ed63b1c1a
|
@ -24,10 +24,11 @@
|
||||||
Tailbone Web API - "Common" Views
|
Tailbone Web API - "Common" Views
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import rattail
|
import rattail
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
from rattail.mail import send_email
|
from rattail.mail import send_email
|
||||||
from rattail.util import OrderedDict
|
|
||||||
|
|
||||||
from cornice import Service
|
from cornice import Service
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ Forms Core
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
@ -346,6 +347,7 @@ class Form(object):
|
||||||
self.schema = schema
|
self.schema = schema
|
||||||
if self.fields is None and self.schema:
|
if self.fields is None and self.schema:
|
||||||
self.set_fields([f.name for f in self.schema])
|
self.set_fields([f.name for f in self.schema])
|
||||||
|
self.grouping = None
|
||||||
self.request = request
|
self.request = request
|
||||||
self.readonly = readonly
|
self.readonly = readonly
|
||||||
self.readonly_fields = set(readonly_fields or [])
|
self.readonly_fields = set(readonly_fields or [])
|
||||||
|
@ -371,6 +373,7 @@ class Form(object):
|
||||||
self.validators = validators or {}
|
self.validators = validators or {}
|
||||||
self.required = required or {}
|
self.required = required or {}
|
||||||
self.helptext = helptext or {}
|
self.helptext = helptext or {}
|
||||||
|
self.dynamic_helptext = {}
|
||||||
self.focus_spec = focus_spec
|
self.focus_spec = focus_spec
|
||||||
self.action_url = action_url
|
self.action_url = action_url
|
||||||
self.cancel_url = cancel_url
|
self.cancel_url = cancel_url
|
||||||
|
@ -404,6 +407,9 @@ class Form(object):
|
||||||
return get_fieldnames(self.request.rattail_config, self.model_class,
|
return get_fieldnames(self.request.rattail_config, self.model_class,
|
||||||
columns=True, proxies=True, relations=True)
|
columns=True, proxies=True, relations=True)
|
||||||
|
|
||||||
|
def set_grouping(self, items):
|
||||||
|
self.grouping = OrderedDict(items)
|
||||||
|
|
||||||
def make_renderers(self):
|
def make_renderers(self):
|
||||||
"""
|
"""
|
||||||
Return a default set of field renderers, based on :attr:`model_class`.
|
Return a default set of field renderers, based on :attr:`model_class`.
|
||||||
|
@ -728,11 +734,15 @@ class Form(object):
|
||||||
"""
|
"""
|
||||||
self.defaults[key] = value
|
self.defaults[key] = value
|
||||||
|
|
||||||
def set_helptext(self, key, value):
|
def set_helptext(self, key, value, dynamic=False):
|
||||||
"""
|
"""
|
||||||
Set the help text for a given field.
|
Set the help text for a given field.
|
||||||
"""
|
"""
|
||||||
self.helptext[key] = value
|
self.helptext[key] = value
|
||||||
|
if value and dynamic:
|
||||||
|
self.dynamic_helptext[key] = True
|
||||||
|
else:
|
||||||
|
self.dynamic_helptext.pop(key, None)
|
||||||
|
|
||||||
def has_helptext(self, key):
|
def has_helptext(self, key):
|
||||||
"""
|
"""
|
||||||
|
@ -935,7 +945,10 @@ class Form(object):
|
||||||
# TODO: older logic did this only if field was *not*
|
# TODO: older logic did this only if field was *not*
|
||||||
# readonly, perhaps should add that back..
|
# readonly, perhaps should add that back..
|
||||||
if self.has_helptext(fieldname):
|
if self.has_helptext(fieldname):
|
||||||
attrs['message'] = self.render_helptext(fieldname)
|
msgkey = 'message'
|
||||||
|
if self.dynamic_helptext.get(fieldname):
|
||||||
|
msgkey = ':message'
|
||||||
|
attrs[msgkey] = self.render_helptext(fieldname)
|
||||||
|
|
||||||
# show errors if present
|
# show errors if present
|
||||||
error_messages = self.get_error_messages(field) if field else None
|
error_messages = self.get_error_messages(field) if field else None
|
||||||
|
|
|
@ -27,11 +27,11 @@ Grid Filters
|
||||||
import re
|
import re
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from rattail.gpc import GPC
|
from rattail.gpc import GPC
|
||||||
from rattail.util import OrderedDict
|
|
||||||
from rattail.core import UNSPECIFIED
|
from rattail.core import UNSPECIFIED
|
||||||
from rattail.time import localtime, make_utc
|
from rattail.time import localtime, make_utc
|
||||||
from rattail.util import prettify
|
from rattail.util import prettify
|
||||||
|
|
|
@ -24,15 +24,13 @@
|
||||||
Template Context Helpers
|
Template Context Helpers
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from rattail.time import localtime, make_utc
|
from rattail.time import localtime, make_utc
|
||||||
from rattail.util import (pretty_quantity, pretty_hours, hours_as_decimal,
|
from rattail.util import pretty_quantity, pretty_hours, hours_as_decimal
|
||||||
OrderedDict)
|
|
||||||
from rattail.db.util import maxlen
|
from rattail.db.util import maxlen
|
||||||
|
|
||||||
from webhelpers2.html import *
|
from webhelpers2.html import *
|
||||||
|
|
|
@ -667,11 +667,18 @@ class MenuHandler(GenericHandler):
|
||||||
'route': 'appinfo',
|
'route': 'appinfo',
|
||||||
'perm': 'appinfo.list',
|
'perm': 'appinfo.list',
|
||||||
},
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
if kwargs.get('include_label_settings', False):
|
||||||
|
items.extend([
|
||||||
{
|
{
|
||||||
'title': "Label Settings",
|
'title': "Label Settings",
|
||||||
'route': 'labelprofiles',
|
'route': 'labelprofiles',
|
||||||
'perm': 'labelprofiles.list',
|
'perm': 'labelprofiles.list',
|
||||||
},
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
items.extend([
|
||||||
{
|
{
|
||||||
'title': "Raw Settings",
|
'title': "Raw Settings",
|
||||||
'route': 'settings',
|
'route': 'settings',
|
||||||
|
@ -807,7 +814,7 @@ def make_menu_entry(request, item):
|
||||||
try:
|
try:
|
||||||
entry['url'] = request.route_url(entry['route'])
|
entry['url'] = request.route_url(entry['route'])
|
||||||
except KeyError: # happens if no such route
|
except KeyError: # happens if no such route
|
||||||
log.debug("invalid route name for menu entry: %s", entry)
|
log.warning("invalid route name for menu entry: %s", entry)
|
||||||
entry['url'] = entry['route']
|
entry['url'] = entry['route']
|
||||||
entry['key'] = entry['route']
|
entry['key'] = entry['route']
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -11,6 +11,19 @@
|
||||||
<section>
|
<section>
|
||||||
% if form_body is not Undefined and form_body:
|
% if form_body is not Undefined and form_body:
|
||||||
${form_body|n}
|
${form_body|n}
|
||||||
|
% elif form.grouping:
|
||||||
|
% for group in form.grouping:
|
||||||
|
<nav class="panel">
|
||||||
|
<p class="panel-heading">${group}</p>
|
||||||
|
<div class="panel-block">
|
||||||
|
<div>
|
||||||
|
% for field in form.grouping[group]:
|
||||||
|
${form.render_buefy_field(field)}
|
||||||
|
% endfor
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
% endfor
|
||||||
% else:
|
% else:
|
||||||
% for field in form.fields:
|
% for field in form.fields:
|
||||||
${form.render_buefy_field(field)}
|
${form.render_buefy_field(field)}
|
||||||
|
|
|
@ -1,480 +0,0 @@
|
||||||
## -*- coding: utf-8; -*-
|
|
||||||
<%inherit file="/page.mako" />
|
|
||||||
|
|
||||||
<%def name="title()">Generate Project</%def>
|
|
||||||
|
|
||||||
<%def name="content_title()"></%def>
|
|
||||||
|
|
||||||
<%def name="page_content()">
|
|
||||||
<b-field horizontal label="Project Type">
|
|
||||||
<b-select v-model="projectType">
|
|
||||||
<option value="rattail">rattail</option>
|
|
||||||
<option value="rattail_integration">rattail-integration</option>
|
|
||||||
<option value="tailbone_integration">tailbone-integration</option>
|
|
||||||
## <option value="byjove">byjove</option>
|
|
||||||
<option value="fabric">fabric</option>
|
|
||||||
</b-select>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<div v-if="projectType == 'rattail'">
|
|
||||||
${h.form(request.current_route_url(), ref='rattailForm')}
|
|
||||||
${h.csrf_token(request)}
|
|
||||||
${h.hidden('project_type', value='rattail')}
|
|
||||||
<br />
|
|
||||||
<div class="card">
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">Naming</p>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<b-field horizontal label="Name"
|
|
||||||
message="The "canonical" name generally used to refer to this project">
|
|
||||||
<b-input name="name" v-model="rattail.name"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Slug"
|
|
||||||
message="Used for e.g. naming the project source code folder">
|
|
||||||
<b-input name="slug" v-model="rattail.slug"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Organization"
|
|
||||||
message="For use with "branding" etc.">
|
|
||||||
<b-input name="organization" v-model="rattail.organization"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Package Name for PyPI"
|
|
||||||
message="It's a good idea to use org name as namespace prefix here">
|
|
||||||
<b-input name="python_project_name" v-model="rattail.python_project_name"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Package Name in Python"
|
|
||||||
:message="`For example, ~/src/${'$'}{rattail.slug}/${'$'}{rattail.python_package_name}/__init__.py`">
|
|
||||||
<b-input name="python_name" v-model="rattail.python_package_name"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div class="card">
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">Database</p>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<b-field horizontal label="Has Rattail DB"
|
|
||||||
message="Note that a DB is required for the Web App">
|
|
||||||
<b-checkbox name="has_db"
|
|
||||||
v-model="rattail.has_rattail_db"
|
|
||||||
native-value="true">
|
|
||||||
</b-checkbox>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Extends Rattail DB Schema"
|
|
||||||
message="For adding custom tables/columns to the core schema">
|
|
||||||
<b-checkbox name="extends_db"
|
|
||||||
v-model="rattail.extends_rattail_db_schema"
|
|
||||||
native-value="true">
|
|
||||||
</b-checkbox>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Uses Rattail Batch Schema"
|
|
||||||
v-show="false"
|
|
||||||
message="Needed for "dynamic" (e.g. import/export) batches">
|
|
||||||
<b-checkbox name="has_batch_schema"
|
|
||||||
v-model="rattail.uses_rattail_batch_schema"
|
|
||||||
native-value="true">
|
|
||||||
</b-checkbox>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div class="card">
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">Web App</p>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<b-field horizontal label="Has Tailbone Web App">
|
|
||||||
<b-checkbox name="has_web"
|
|
||||||
v-model="rattail.has_tailbone_web_app"
|
|
||||||
native-value="true">
|
|
||||||
</b-checkbox>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Has Tailbone Web API"
|
|
||||||
v-show="false"
|
|
||||||
message="Needed for e.g. Vue.js SPA mobile apps">
|
|
||||||
<b-checkbox name="has_web_api"
|
|
||||||
v-model="rattail.has_tailbone_web_api"
|
|
||||||
native-value="true">
|
|
||||||
</b-checkbox>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div class="card">
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">Integrations</p>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<b-field horizontal label="Integrates w/ Catapult"
|
|
||||||
message="Add schema, import/export logic etc. for ECRS Catapult">
|
|
||||||
<b-checkbox name="integrates_catapult"
|
|
||||||
v-model="rattail.integrates_with_catapult"
|
|
||||||
native-value="true">
|
|
||||||
</b-checkbox>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Integrates w/ CORE-POS"
|
|
||||||
v-show="false">
|
|
||||||
<b-checkbox name="integrates_corepos"
|
|
||||||
v-model="rattail.integrates_with_corepos"
|
|
||||||
native-value="true">
|
|
||||||
</b-checkbox>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Integrates w/ LOC SMS"
|
|
||||||
message="Add schema, import/export logic etc. for LOC SMS">
|
|
||||||
<b-checkbox name="integrates_locsms"
|
|
||||||
v-model="rattail.integrates_with_locsms"
|
|
||||||
native-value="true">
|
|
||||||
</b-checkbox>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Has DataSync Service"
|
|
||||||
v-show="false">
|
|
||||||
<b-checkbox name="has_datasync"
|
|
||||||
v-model="rattail.has_datasync_service"
|
|
||||||
native-value="true">
|
|
||||||
</b-checkbox>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div class="card">
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">Deployment</p>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<b-field horizontal label="Uses Fabric">
|
|
||||||
<b-checkbox name="uses_fabric"
|
|
||||||
v-model="rattail.uses_fabric"
|
|
||||||
native-value="true">
|
|
||||||
</b-checkbox>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
${h.end_form()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="projectType == 'rattail_integration'">
|
|
||||||
${h.form(request.current_route_url(), ref='rattail_integrationForm')}
|
|
||||||
${h.csrf_token(request)}
|
|
||||||
${h.hidden('project_type', value='rattail_integration')}
|
|
||||||
<br />
|
|
||||||
<div class="card">
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">Naming</p>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<b-field horizontal label="Integration Name"
|
|
||||||
message="Name of the system to be integrated">
|
|
||||||
<b-input name="integration_name" v-model="rattail_integration.integration_name"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Integration URL"
|
|
||||||
message="Reference URL for the system to be integrated">
|
|
||||||
<b-input name="integration_url" v-model="rattail_integration.integration_url"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Package Name for PyPI"
|
|
||||||
message="Also will be used as slug, e.g. for folder name">
|
|
||||||
<b-input name="python_project_name" v-model="rattail_integration.python_project_name"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
${h.hidden('slug', **{'v-model': 'rattail_integration.python_project_name'})}
|
|
||||||
|
|
||||||
<b-field horizontal label="Package Name in Python"
|
|
||||||
:message="`For example, ~/src/${'$'}{rattail_integration.python_project_name}/${'$'}{rattail_integration.python_package_name}/__init__.py`">
|
|
||||||
<b-input name="python_name" v-model="rattail_integration.python_package_name"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div class="card">
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">Options</p>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<b-field horizontal label="Extends Config"
|
|
||||||
message="Adds custom config extension">
|
|
||||||
<b-checkbox name="extends_config"
|
|
||||||
v-model="rattail_integration.extends_config"
|
|
||||||
native-value="true">
|
|
||||||
</b-checkbox>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Extends Rattail Schema"
|
|
||||||
message="Adds custom tables/columns to the Rattail DB schema">
|
|
||||||
<b-checkbox name="extends_db"
|
|
||||||
v-model="rattail_integration.extends_db"
|
|
||||||
native-value="true">
|
|
||||||
</b-checkbox>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
${h.end_form()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="projectType == 'tailbone_integration'">
|
|
||||||
${h.form(request.current_route_url(), ref='tailbone_integrationForm')}
|
|
||||||
${h.csrf_token(request)}
|
|
||||||
${h.hidden('project_type', value='tailbone_integration')}
|
|
||||||
<br />
|
|
||||||
<div class="card">
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">Naming</p>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<b-field horizontal label="Integration Name"
|
|
||||||
message="Name of the system to be integrated">
|
|
||||||
<b-input name="integration_name" v-model="tailbone_integration.integration_name"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Integration URL"
|
|
||||||
message="Reference URL for the system to be integrated">
|
|
||||||
<b-input name="integration_url" v-model="tailbone_integration.integration_url"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Package Name for PyPI"
|
|
||||||
message="Also will be used as slug, e.g. for folder name">
|
|
||||||
<b-input name="python_project_name" v-model="tailbone_integration.python_project_name"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
${h.hidden('slug', **{'v-model': 'tailbone_integration.python_project_name'})}
|
|
||||||
|
|
||||||
<b-field horizontal label="Package Name in Python"
|
|
||||||
:message="`For example, ~/src/${'$'}{tailbone_integration.python_project_name}/${'$'}{tailbone_integration.python_package_name}/__init__.py`">
|
|
||||||
<b-input name="python_name" v-model="tailbone_integration.python_package_name"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div class="card">
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">Options</p>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<b-field horizontal label="Has Static Files"
|
|
||||||
message="Register a subfolder for static files (images etc.)">
|
|
||||||
<b-checkbox name="has_static_files"
|
|
||||||
v-model="tailbone_integration.has_static_files"
|
|
||||||
native-value="true">
|
|
||||||
</b-checkbox>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
${h.end_form()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="projectType == 'byjove'">
|
|
||||||
${h.form(request.current_route_url(), ref='byjoveForm')}
|
|
||||||
${h.csrf_token(request)}
|
|
||||||
${h.hidden('project_type', value='byjove')}
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<div class="card">
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">Naming</p>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<b-field horizontal label="Name">
|
|
||||||
<b-input name="name" v-model="byjove.name"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Slug">
|
|
||||||
<b-input name="slug" v-model="byjove.slug"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${h.end_form()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="projectType == 'fabric'">
|
|
||||||
${h.form(request.current_route_url(), ref='fabricForm')}
|
|
||||||
${h.csrf_token(request)}
|
|
||||||
${h.hidden('project_type', value='fabric')}
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<div class="card">
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">Naming</p>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<b-field horizontal label="Name"
|
|
||||||
message="The "canonical" name generally used to refer to this project">
|
|
||||||
<b-input name="name" v-model="fabric.name"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Slug"
|
|
||||||
message="Used for e.g. naming the project source code folder">
|
|
||||||
<b-input name="slug" v-model="fabric.slug"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Organization"
|
|
||||||
message="For use with "branding" etc.">
|
|
||||||
<b-input name="organization" v-model="fabric.organization"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Package Name for PyPI"
|
|
||||||
message="It's a good idea to use org name as namespace prefix here">
|
|
||||||
<b-input name="python_project_name" v-model="fabric.python_project_name"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-field horizontal label="Package Name in Python"
|
|
||||||
:message="`For example, ~/src/${'$'}{fabric.slug}/${'$'}{fabric.python_package_name}/__init__.py`">
|
|
||||||
<b-input name="python_name" v-model="fabric.python_package_name"></b-input>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<div class="card">
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">Theo</p>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<b-field horizontal label="Integrates With"
|
|
||||||
message="Which POS system should Theo integrate with, if any">
|
|
||||||
<b-select name="integrates_with" v-model="fabric.integrates_with">
|
|
||||||
<option value="">(nothing)</option>
|
|
||||||
<option value="catapult">ECRS Catapult</option>
|
|
||||||
<option value="corepos">CORE-POS</option>
|
|
||||||
## <option value="locsms">LOC SMS</option>
|
|
||||||
</b-select>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${h.end_form()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<div class="buttons" style="padding-left: 8rem;">
|
|
||||||
<b-button type="is-primary"
|
|
||||||
@click="submitProjectForm()">
|
|
||||||
Generate Project
|
|
||||||
</b-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="modify_this_page_vars()">
|
|
||||||
${parent.modify_this_page_vars()}
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
ThisPageData.projectType = 'rattail'
|
|
||||||
|
|
||||||
ThisPageData.rattail = {
|
|
||||||
name: "Okay-Then",
|
|
||||||
slug: "okay-then",
|
|
||||||
organization: "Acme Foods",
|
|
||||||
python_project_name: "Acme-Okay-Then",
|
|
||||||
python_package_name: "okay_then",
|
|
||||||
has_rattail_db: true,
|
|
||||||
extends_rattail_db_schema: true,
|
|
||||||
uses_rattail_batch_schema: false,
|
|
||||||
has_tailbone_web_app: true,
|
|
||||||
has_tailbone_web_api: false,
|
|
||||||
has_datasync_service: false,
|
|
||||||
integrates_with_catapult: false,
|
|
||||||
integrates_with_corepos: false,
|
|
||||||
integrates_with_locsms: false,
|
|
||||||
uses_fabric: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
ThisPageData.rattail_integration = {
|
|
||||||
integration_name: "Foo",
|
|
||||||
integration_url: "https://www.example.com/",
|
|
||||||
python_project_name: "rattail-foo",
|
|
||||||
python_package_name: "rattail_foo",
|
|
||||||
extends_config: true,
|
|
||||||
extends_db: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
ThisPageData.tailbone_integration = {
|
|
||||||
integration_name: "Foo",
|
|
||||||
integration_url: "https://www.example.com/",
|
|
||||||
python_project_name: "tailbone-foo",
|
|
||||||
python_package_name: "tailbone_foo",
|
|
||||||
}
|
|
||||||
|
|
||||||
ThisPageData.byjove = {
|
|
||||||
name: "Okay-Then-Mobile",
|
|
||||||
slug: "okay-then-mobile",
|
|
||||||
}
|
|
||||||
|
|
||||||
ThisPageData.fabric = {
|
|
||||||
name: "AcmeFab",
|
|
||||||
slug: "acmefab",
|
|
||||||
organization: "Acme Foods",
|
|
||||||
python_project_name: "Acme-Fabric",
|
|
||||||
python_package_name: "acmefab",
|
|
||||||
integrates_with: '',
|
|
||||||
}
|
|
||||||
|
|
||||||
ThisPage.methods.submitProjectForm = function() {
|
|
||||||
let form = this.$refs[this.projectType + 'Form']
|
|
||||||
form.submit()
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
|
|
||||||
${parent.body()}
|
|
24
tailbone/templates/generated-projects/create.mako
Normal file
24
tailbone/templates/generated-projects/create.mako
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/master/create.mako" />
|
||||||
|
|
||||||
|
<%def name="title()">${index_title}</%def>
|
||||||
|
|
||||||
|
<%def name="content_title()"></%def>
|
||||||
|
|
||||||
|
<%def name="page_content()">
|
||||||
|
% if project_type:
|
||||||
|
<b-field grouped>
|
||||||
|
<b-field horizontal expanded label="Project Type">
|
||||||
|
${project_type}
|
||||||
|
</b-field>
|
||||||
|
<once-button type="is-primary"
|
||||||
|
tag="a" href="${url('generated_projects.create')}"
|
||||||
|
text="Start Over">
|
||||||
|
</once-button>
|
||||||
|
</b-field>
|
||||||
|
% endif
|
||||||
|
${parent.page_content()}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
${parent.body()}
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2022 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -24,10 +24,9 @@
|
||||||
Views for handheld batches
|
Views for handheld batches
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
from collections import OrderedDict
|
||||||
|
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
from rattail.util import OrderedDict
|
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
from webhelpers2.html import tags
|
from webhelpers2.html import tags
|
||||||
|
|
|
@ -27,12 +27,13 @@ Views for inventory batches
|
||||||
import re
|
import re
|
||||||
import decimal
|
import decimal
|
||||||
import logging
|
import logging
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from rattail import pod
|
from rattail import pod
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
from rattail.db.util import make_full_description
|
from rattail.db.util import make_full_description
|
||||||
from rattail.gpc import GPC
|
from rattail.gpc import GPC
|
||||||
from rattail.util import pretty_quantity, OrderedDict
|
from rattail.util import pretty_quantity
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
from deform import widget as dfwidget
|
from deform import widget as dfwidget
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2020 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -24,10 +24,9 @@
|
||||||
Views for generic product batches
|
Views for generic product batches
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
from collections import OrderedDict
|
||||||
|
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
from rattail.util import OrderedDict
|
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
from webhelpers2.html import HTML
|
from webhelpers2.html import HTML
|
||||||
|
|
|
@ -25,9 +25,10 @@ Various common views
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from rattail.batch import consume_batch_id
|
from rattail.batch import consume_batch_id
|
||||||
from rattail.util import OrderedDict, simple_error, import_module_path
|
from rattail.util import simple_error, import_module_path
|
||||||
from rattail.files import resource_path
|
from rattail.files import resource_path
|
||||||
|
|
||||||
from pyramid import httpexceptions
|
from pyramid import httpexceptions
|
||||||
|
|
|
@ -32,6 +32,7 @@ import getpass
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import logging
|
import logging
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
@ -41,7 +42,7 @@ from sqlalchemy_utils.functions import get_primary_keys, get_columns
|
||||||
|
|
||||||
from rattail.db import model, Session as RattailSession
|
from rattail.db import model, Session as RattailSession
|
||||||
from rattail.db.continuum import model_transaction_query
|
from rattail.db.continuum import model_transaction_query
|
||||||
from rattail.util import prettify, OrderedDict, simple_error
|
from rattail.util import prettify, simple_error, get_class_hierarchy
|
||||||
from rattail.time import localtime
|
from rattail.time import localtime
|
||||||
from rattail.threads import Thread
|
from rattail.threads import Thread
|
||||||
from rattail.csvutil import UnicodeDictWriter
|
from rattail.csvutil import UnicodeDictWriter
|
||||||
|
@ -268,17 +269,7 @@ class MasterView(View):
|
||||||
return labels
|
return labels
|
||||||
|
|
||||||
def get_class_hierarchy(self):
|
def get_class_hierarchy(self):
|
||||||
hierarchy = []
|
return get_class_hierarchy(self.__class__)
|
||||||
|
|
||||||
def traverse(cls):
|
|
||||||
if cls is not object:
|
|
||||||
hierarchy.append(cls)
|
|
||||||
for parent in cls.__bases__:
|
|
||||||
traverse(parent)
|
|
||||||
|
|
||||||
traverse(self.__class__)
|
|
||||||
hierarchy.reverse()
|
|
||||||
return hierarchy
|
|
||||||
|
|
||||||
def set_row_labels(self, obj):
|
def set_row_labels(self, obj):
|
||||||
labels = self.collect_row_labels()
|
labels = self.collect_row_labels()
|
||||||
|
@ -2215,6 +2206,7 @@ class MasterView(View):
|
||||||
"""
|
"""
|
||||||
Returns the master view's index URL.
|
Returns the master view's index URL.
|
||||||
"""
|
"""
|
||||||
|
if self.listable:
|
||||||
route = self.get_route_prefix()
|
route = self.get_route_prefix()
|
||||||
return self.request.route_url(route, **kwargs)
|
return self.request.route_url(route, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ Person Views
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
@ -33,7 +34,7 @@ from sqlalchemy import orm
|
||||||
from rattail.db import model, api
|
from rattail.db import model, api
|
||||||
from rattail.db.util import maxlen
|
from rattail.db.util import maxlen
|
||||||
from rattail.time import localtime
|
from rattail.time import localtime
|
||||||
from rattail.util import OrderedDict, simple_error
|
from rattail.util import simple_error
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
|
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
|
||||||
|
|
|
@ -25,9 +25,9 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from rattail.core import Object
|
from rattail.core import Object
|
||||||
from rattail.util import OrderedDict
|
|
||||||
|
|
||||||
from webhelpers2.html import HTML
|
from webhelpers2.html import HTML
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ Product Views
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
|
from collections import OrderedDict
|
||||||
import humanize
|
import humanize
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
@ -37,7 +37,7 @@ from rattail.db import model, api, auth, Session as RattailSession
|
||||||
from rattail.gpc import GPC
|
from rattail.gpc import GPC
|
||||||
from rattail.threads import Thread
|
from rattail.threads import Thread
|
||||||
from rattail.exceptions import LabelPrintingError
|
from rattail.exceptions import LabelPrintingError
|
||||||
from rattail.util import load_object, pretty_quantity, OrderedDict, simple_error
|
from rattail.util import load_object, pretty_quantity, simple_error
|
||||||
from rattail.time import localtime, make_utc
|
from rattail.time import localtime, make_utc
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
|
|
|
@ -24,207 +24,358 @@
|
||||||
Project views
|
Project views
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
from collections import OrderedDict
|
||||||
import zipfile
|
|
||||||
# from collections import OrderedDict
|
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
|
from deform import widget as dfwidget
|
||||||
|
|
||||||
|
from rattail.projects import PythonProjectGenerator, PoserProjectGenerator
|
||||||
|
|
||||||
from tailbone import forms
|
from tailbone import forms
|
||||||
from tailbone.views import View
|
from tailbone.views import MasterView
|
||||||
|
|
||||||
|
|
||||||
class GenerateProject(colander.MappingSchema):
|
class GeneratedProjectView(MasterView):
|
||||||
"""
|
|
||||||
Base schema for the "generate project" form
|
|
||||||
"""
|
|
||||||
name = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
slug = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
organization = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
python_project_name = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
python_name = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
has_db = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
extends_db = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
has_batch_schema = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
has_web = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
has_web_api = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
has_datasync = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
# has_filemon = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
# has_tempmon = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
# has_bouncer = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
integrates_catapult = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
integrates_corepos = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
# integrates_instacart = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
integrates_locsms = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
# integrates_mailchimp = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
uses_fabric = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
|
|
||||||
class GenerateRattailIntegrationProject(colander.MappingSchema):
|
|
||||||
"""
|
|
||||||
Schema to generate new rattail-integration project
|
|
||||||
"""
|
|
||||||
integration_name = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
integration_url = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
slug = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
python_project_name = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
python_name = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
extends_config = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
extends_db = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
|
|
||||||
class GenerateTailboneIntegrationProject(colander.MappingSchema):
|
|
||||||
"""
|
|
||||||
Schema to generate new tailbone-integration project
|
|
||||||
"""
|
|
||||||
integration_name = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
integration_url = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
slug = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
python_project_name = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
python_name = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
has_static_files = colander.SchemaNode(colander.Boolean())
|
|
||||||
|
|
||||||
|
|
||||||
class GenerateByjoveProject(colander.MappingSchema):
|
|
||||||
"""
|
|
||||||
Schema for generating a new 'byjove' project
|
|
||||||
"""
|
|
||||||
name = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
slug = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
|
|
||||||
class GenerateFabricProject(colander.MappingSchema):
|
|
||||||
"""
|
|
||||||
Schema for generating a new 'fabric' project
|
|
||||||
"""
|
|
||||||
name = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
slug = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
organization = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
python_project_name = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
python_name = colander.SchemaNode(colander.String())
|
|
||||||
|
|
||||||
integrates_with = colander.SchemaNode(colander.String(),
|
|
||||||
missing=colander.null)
|
|
||||||
|
|
||||||
|
|
||||||
class GenerateProjectView(View):
|
|
||||||
"""
|
"""
|
||||||
View for generating new project source code
|
View for generating new project source code
|
||||||
"""
|
"""
|
||||||
|
model_title = "Generated Project"
|
||||||
|
model_key = 'folder'
|
||||||
|
route_prefix = 'generated_projects'
|
||||||
|
url_prefix = '/generated-projects'
|
||||||
|
listable = False
|
||||||
|
viewable = False
|
||||||
|
editable = False
|
||||||
|
deletable = False
|
||||||
|
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
super(GenerateProjectView, self).__init__(request)
|
super(GeneratedProjectView, self).__init__(request)
|
||||||
self.project_handler = self.get_handler()
|
self.project_handler = self.get_project_handler()
|
||||||
# TODO: deprecate / remove this
|
|
||||||
self.handler = self.project_handler
|
|
||||||
|
|
||||||
def get_handler(self):
|
def get_project_handler(self):
|
||||||
from rattail.projects.handler import RattailProjectHandler
|
app = self.get_rattail_app()
|
||||||
return RattailProjectHandler(self.rattail_config)
|
return app.get_project_handler()
|
||||||
|
|
||||||
def __call__(self):
|
def create(self):
|
||||||
|
supported = self.project_handler.get_supported_project_generators()
|
||||||
|
supported_keys = list(supported)
|
||||||
|
|
||||||
# choices = OrderedDict([
|
project_type = self.request.matchdict.get('project_type')
|
||||||
# ('has_db', {'prompt': "Does project need its own Rattail DB?",
|
if project_type:
|
||||||
# 'type': 'bool'}),
|
form = self.make_project_form(project_type)
|
||||||
# ])
|
|
||||||
|
|
||||||
project_type = 'rattail'
|
|
||||||
if self.request.method == 'POST':
|
|
||||||
project_type = self.request.POST.get('project_type', 'rattail')
|
|
||||||
if project_type not in self.project_handler.get_supported_project_types():
|
|
||||||
raise ValueError("Unknown project type: {}".format(project_type))
|
|
||||||
|
|
||||||
if project_type == 'byjove':
|
|
||||||
schema = GenerateByjoveProject
|
|
||||||
elif project_type == 'fabric':
|
|
||||||
schema = GenerateFabricProject
|
|
||||||
elif project_type == 'rattail_integration':
|
|
||||||
schema = GenerateRattailIntegrationProject
|
|
||||||
elif project_type == 'tailbone_integration':
|
|
||||||
schema = GenerateTailboneIntegrationProject
|
|
||||||
else:
|
|
||||||
schema = GenerateProject
|
|
||||||
form = forms.Form(schema=schema(), request=self.request)
|
|
||||||
if form.validate(newstyle=True):
|
if form.validate(newstyle=True):
|
||||||
zipped = self.generate_project(project_type, form)
|
zipped = self.generate_project(project_type, form)
|
||||||
return self.file_response(zipped)
|
return self.file_response(zipped)
|
||||||
# self.request.session.flash("New project was generated: {}".format(form.validated['name']))
|
|
||||||
# return self.redirect(self.request.current_route_url())
|
|
||||||
|
|
||||||
return {
|
else: # no project_type
|
||||||
|
|
||||||
|
# make form to accept user choice of report type
|
||||||
|
schema = colander.Schema()
|
||||||
|
values = [(typ, typ) for typ in supported_keys]
|
||||||
|
schema.add(colander.SchemaNode(name='project_type',
|
||||||
|
typ=colander.String(),
|
||||||
|
validator=colander.OneOf(supported_keys),
|
||||||
|
widget=dfwidget.SelectWidget(values=values)))
|
||||||
|
form = forms.Form(schema=schema, request=self.request)
|
||||||
|
form.submit_label = "Continue"
|
||||||
|
|
||||||
|
# if form validates, then user has chosen a project type, so
|
||||||
|
# we redirect to the appropriate "generate project" page
|
||||||
|
if form.validate(newstyle=True):
|
||||||
|
raise self.redirect(self.request.route_url(
|
||||||
|
'generate_specific_project',
|
||||||
|
project_type=form.validated['project_type']))
|
||||||
|
|
||||||
|
return self.render_to_response('create', {
|
||||||
'index_title': "Generate Project",
|
'index_title': "Generate Project",
|
||||||
'handler': self.handler,
|
'project_type': project_type,
|
||||||
# 'choices': choices,
|
'form': form,
|
||||||
}
|
})
|
||||||
|
|
||||||
def generate_project(self, project_type, form):
|
def generate_project(self, project_type, form):
|
||||||
options = form.validated
|
context = dict(form.validated)
|
||||||
slug = options['slug']
|
output = self.project_handler.generate_project(project_type,
|
||||||
path = self.handler.generate_project(project_type, slug, options)
|
context=context)
|
||||||
|
return self.project_handler.zip_output(output)
|
||||||
|
|
||||||
zipped = '{}.zip'.format(path)
|
def make_project_form(self, project_type):
|
||||||
with zipfile.ZipFile(zipped, 'w', zipfile.ZIP_DEFLATED) as z:
|
|
||||||
self.zipdir(z, path, slug)
|
|
||||||
return zipped
|
|
||||||
|
|
||||||
def zipdir(self, zipf, path, slug):
|
# make form
|
||||||
for root, dirs, files in os.walk(path):
|
schema = self.project_handler.make_project_schema(project_type)
|
||||||
relative_root = os.path.join(slug, root[len(path)+1:])
|
form = forms.Form(schema=schema, request=self.request)
|
||||||
for fname in files:
|
form.auto_disable = False
|
||||||
zipf.write(os.path.join(root, fname),
|
form.auto_disable_save = False
|
||||||
arcname=os.path.join(relative_root, fname))
|
form.submit_label = "Generate Project"
|
||||||
|
form.cancel_url = self.request.route_url('generated_projects.create')
|
||||||
|
|
||||||
|
# apply normal config
|
||||||
|
self.configure_form_common(form, project_type)
|
||||||
|
|
||||||
|
# let supplemental views further configure form
|
||||||
|
for supp in self.iter_view_supplements():
|
||||||
|
configure = getattr(supp, 'configure_form_{}'.format(project_type), None)
|
||||||
|
if configure:
|
||||||
|
configure(form)
|
||||||
|
|
||||||
|
# if master view has more configure logic, do that too
|
||||||
|
configure = getattr(self, 'configure_form_{}'.format(project_type), None)
|
||||||
|
if configure:
|
||||||
|
configure(form)
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
def configure_form_common(self, form, project_type):
|
||||||
|
generator = self.project_handler.get_project_generator(project_type,
|
||||||
|
require=True)
|
||||||
|
|
||||||
|
# python-based projects
|
||||||
|
if isinstance(generator, PythonProjectGenerator):
|
||||||
|
self.configure_form_python(form)
|
||||||
|
|
||||||
|
# poser-based projects
|
||||||
|
if isinstance(generator, PoserProjectGenerator):
|
||||||
|
self.configure_form_poser(form)
|
||||||
|
|
||||||
|
def configure_form_python(self, f):
|
||||||
|
|
||||||
|
f.set_grouping([
|
||||||
|
("Naming", [
|
||||||
|
'name',
|
||||||
|
'pkg_name',
|
||||||
|
'pypi_name',
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
|
||||||
|
# name
|
||||||
|
f.set_label('name', "Project Name")
|
||||||
|
f.set_helptext('name', "Human-friendly name generally used to refer to this project.")
|
||||||
|
f.set_default('name', "Poser Plus")
|
||||||
|
|
||||||
|
# pkg_name
|
||||||
|
f.set_label('pkg_name', "Package Name in Python")
|
||||||
|
f.set_helptext('pkg_name', "`For example, ~/src/${field_model_pkg_name.replace(/_/g, '-')}/${field_model_pkg_name}/__init__.py`",
|
||||||
|
dynamic=True)
|
||||||
|
f.set_default('pkg_name', "poser_plus")
|
||||||
|
|
||||||
|
# pypi_name
|
||||||
|
f.set_label('pypi_name', "Package Name for PyPI")
|
||||||
|
f.set_helptext('pypi_name', "It's a good idea to use org name as namespace prefix here")
|
||||||
|
f.set_default('pypi_name', "Acme-Poser-Plus")
|
||||||
|
|
||||||
|
def configure_form_poser(self, f):
|
||||||
|
|
||||||
|
# extends_config
|
||||||
|
f.set_label('extends_config', "Extend Config")
|
||||||
|
f.set_helptext('extends_config', "Needed to customize default config values etc.")
|
||||||
|
f.set_default('extends_config', True)
|
||||||
|
|
||||||
|
# has_cli
|
||||||
|
f.set_label('has_cli', "Use Separate CLI")
|
||||||
|
f.set_helptext('has_cli', "`Needed for e.g. '${field_model_pkg_name} install' command.`",
|
||||||
|
dynamic=True)
|
||||||
|
f.set_default('has_cli', True)
|
||||||
|
|
||||||
|
# organization
|
||||||
|
f.set_helptext('organization', 'For use with branding etc.')
|
||||||
|
f.set_default('organization', "Acme Foods")
|
||||||
|
|
||||||
|
# has_db
|
||||||
|
f.set_label('has_db', "Use Rattail DB")
|
||||||
|
f.set_helptext('has_db', "Note that a DB is required for the Web App")
|
||||||
|
f.set_default('has_db', True)
|
||||||
|
|
||||||
|
# extends_db
|
||||||
|
f.set_label('extends_db', "Extend DB Schema")
|
||||||
|
f.set_helptext('extends_db', "For adding custom tables/columns to the core schema")
|
||||||
|
f.set_default('extends_db', True)
|
||||||
|
|
||||||
|
# has_batch_schema
|
||||||
|
f.set_label('has_batch_schema', "Add Batch Schema")
|
||||||
|
f.set_helptext('has_batch_schema', 'Usually not needed - it\'s for "dynamic" (e.g. import/export) batches')
|
||||||
|
|
||||||
|
# has_web
|
||||||
|
f.set_label('has_web', "Use Tailbone Web App")
|
||||||
|
f.set_default('has_web', True)
|
||||||
|
|
||||||
|
# has_web_api
|
||||||
|
f.set_label('has_web_api', "Use Tailbone Web API")
|
||||||
|
f.set_helptext('has_web_api', "Needed for e.g. Vue.js SPA mobile apps")
|
||||||
|
|
||||||
|
# has_datasync
|
||||||
|
f.set_label('has_datasync', "Use DataSync Service")
|
||||||
|
|
||||||
|
# uses_fabric
|
||||||
|
f.set_label('uses_fabric', "Use Fabric")
|
||||||
|
f.set_default('uses_fabric', True)
|
||||||
|
|
||||||
|
def configure_form_rattail(self, f):
|
||||||
|
|
||||||
|
f.set_grouping([
|
||||||
|
("Naming", [
|
||||||
|
'name',
|
||||||
|
'pkg_name',
|
||||||
|
'pypi_name',
|
||||||
|
'organization',
|
||||||
|
]),
|
||||||
|
("Core", [
|
||||||
|
'extends_config',
|
||||||
|
'has_cli',
|
||||||
|
]),
|
||||||
|
("Database", [
|
||||||
|
'has_db',
|
||||||
|
'extends_db',
|
||||||
|
'has_batch_schema',
|
||||||
|
]),
|
||||||
|
("Web", [
|
||||||
|
'has_web',
|
||||||
|
'has_web_api',
|
||||||
|
]),
|
||||||
|
("Integrations", [
|
||||||
|
# 'integrates_catapult',
|
||||||
|
# 'integrates_corepos',
|
||||||
|
# 'integrates_locsms',
|
||||||
|
'has_datasync',
|
||||||
|
]),
|
||||||
|
("Deployment", [
|
||||||
|
'uses_fabric',
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
|
||||||
|
# # integrates_catapult
|
||||||
|
# f.set_label('integrates_catapult', "Integrate w/ Catapult")
|
||||||
|
# f.set_helptext('integrates_catapult', "Add schema, import/export logic etc. for ECRS Catapult")
|
||||||
|
|
||||||
|
# # integrates_corepos
|
||||||
|
# f.set_label('integrates_corepos', "Integrate w/ CORE-POS")
|
||||||
|
# f.set_helptext('integrates_corepos', "Add schema, import/export logic etc. for CORE-POS")
|
||||||
|
|
||||||
|
# # integrates_locsms
|
||||||
|
# f.set_label('integrates_locsms', "Integrate w/ LOC SMS")
|
||||||
|
# f.set_helptext('integrates_locsms', "Add schema, import/export logic etc. for LOC SMS")
|
||||||
|
|
||||||
|
def configure_form_rattail_integration(self, f):
|
||||||
|
|
||||||
|
f.set_grouping([
|
||||||
|
("Naming", [
|
||||||
|
'integration_name',
|
||||||
|
'integration_url',
|
||||||
|
'name',
|
||||||
|
'pkg_name',
|
||||||
|
'pypi_name',
|
||||||
|
]),
|
||||||
|
("Options", [
|
||||||
|
'extends_config',
|
||||||
|
'extends_db',
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
|
||||||
|
# integration_name
|
||||||
|
f.set_helptext('integration_name', "Name of the system to be integrated")
|
||||||
|
f.set_default('integration_name', "Foo")
|
||||||
|
|
||||||
|
# integration_url
|
||||||
|
f.set_label('integration_url', "Integration URL")
|
||||||
|
f.set_helptext('integration_url', "Reference URL for the system to be integrated")
|
||||||
|
f.set_default('integration_url', "https://www.example.com/")
|
||||||
|
|
||||||
|
def configure_form_tailbone_integration(self, f):
|
||||||
|
|
||||||
|
f.set_grouping([
|
||||||
|
("Naming", [
|
||||||
|
'integration_name',
|
||||||
|
'integration_url',
|
||||||
|
'name',
|
||||||
|
'pkg_name',
|
||||||
|
'pypi_name',
|
||||||
|
]),
|
||||||
|
("Options", [
|
||||||
|
'has_static_files',
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
|
||||||
|
# integration_name
|
||||||
|
f.set_helptext('integration_name', "Name of the system to be integrated")
|
||||||
|
f.set_default('integration_name', "Foo")
|
||||||
|
|
||||||
|
# integration_url
|
||||||
|
f.set_label('integration_url', "Integration URL")
|
||||||
|
f.set_helptext('integration_url', "Reference URL for the system to be integrated")
|
||||||
|
f.set_default('integration_url', "https://www.example.com/")
|
||||||
|
|
||||||
|
# has_static_files
|
||||||
|
f.set_helptext('has_static_files', "Register a subfolder for static files (images etc.)")
|
||||||
|
|
||||||
|
def configure_form_byjove(self, f):
|
||||||
|
|
||||||
|
f.set_grouping([
|
||||||
|
("Naming", [
|
||||||
|
'name',
|
||||||
|
'slug',
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
|
||||||
|
# name
|
||||||
|
f.set_default('name', "Okay Then Mobile")
|
||||||
|
|
||||||
|
# slug
|
||||||
|
f.set_default('slug', "okay-then-mobile")
|
||||||
|
|
||||||
|
def configure_form_fabric(self, f):
|
||||||
|
|
||||||
|
f.set_grouping([
|
||||||
|
("Naming", [
|
||||||
|
'name',
|
||||||
|
'pkg_name',
|
||||||
|
'pypi_name',
|
||||||
|
'organization',
|
||||||
|
]),
|
||||||
|
("Theo", [
|
||||||
|
'integrates_with',
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
|
||||||
|
# naming defaults
|
||||||
|
f.set_default('name', "Acme Fabric")
|
||||||
|
f.set_default('pkg_name', "acmefab")
|
||||||
|
f.set_default('pypi_name', "Acme-Fabric")
|
||||||
|
|
||||||
|
# organization
|
||||||
|
f.set_helptext('organization', 'For use with branding etc.')
|
||||||
|
f.set_default('organization', "Acme Foods")
|
||||||
|
|
||||||
|
# integrates_with
|
||||||
|
f.set_helptext('integrates_with', "Which POS system should Theo integrate with, if any")
|
||||||
|
f.set_enum('integrates_with', OrderedDict([
|
||||||
|
('', "(nothing)"),
|
||||||
|
('catapult', "ECRS Catapult"),
|
||||||
|
('corepos', "CORE-POS"),
|
||||||
|
('locsms', "LOC SMS")
|
||||||
|
]))
|
||||||
|
f.set_default('integrates_with', '')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def defaults(cls, config):
|
def defaults(cls, config):
|
||||||
config.add_tailbone_permission('common', 'common.generate_project',
|
cls._defaults(config)
|
||||||
"Generate new project source code")
|
cls._generated_project_defaults(config)
|
||||||
config.add_route('generate_project', '/generate-project')
|
|
||||||
config.add_view(cls, route_name='generate_project',
|
@classmethod
|
||||||
permission='common.generate_project',
|
def _generated_project_defaults(cls, config):
|
||||||
renderer='/generate_project.mako')
|
url_prefix = cls.get_url_prefix()
|
||||||
|
permission_prefix = cls.get_permission_prefix()
|
||||||
|
|
||||||
|
# generate project (accept custom params, truly create)
|
||||||
|
config.add_route('generate_specific_project',
|
||||||
|
'{}/new/{{project_type}}'.format(url_prefix))
|
||||||
|
config.add_view(cls, attr='create',
|
||||||
|
route_name='generate_specific_project',
|
||||||
|
permission='{}.create'.format(permission_prefix))
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
||||||
GenerateProjectView = kwargs.get('GenerateProjectView', base['GenerateProjectView'])
|
GeneratedProjectView = kwargs.get('GeneratedProjectView', base['GeneratedProjectView'])
|
||||||
GenerateProjectView.defaults(config)
|
GeneratedProjectView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
|
|
|
@ -28,6 +28,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import decimal
|
import decimal
|
||||||
import logging
|
import logging
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import humanize
|
import humanize
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
@ -35,7 +36,7 @@ import sqlalchemy as sa
|
||||||
from rattail import pod
|
from rattail import pod
|
||||||
from rattail.db import model, Session as RattailSession
|
from rattail.db import model, Session as RattailSession
|
||||||
from rattail.time import localtime, make_utc
|
from rattail.time import localtime, make_utc
|
||||||
from rattail.util import pretty_quantity, prettify, OrderedDict, simple_error
|
from rattail.util import pretty_quantity, prettify, simple_error
|
||||||
from rattail.threads import Thread
|
from rattail.threads import Thread
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
|
|
|
@ -29,13 +29,14 @@ import json
|
||||||
import re
|
import re
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import rattail
|
import rattail
|
||||||
from rattail.db import model, Session as RattailSession
|
from rattail.db import model, Session as RattailSession
|
||||||
from rattail.files import resource_path
|
from rattail.files import resource_path
|
||||||
from rattail.time import localtime
|
from rattail.time import localtime
|
||||||
from rattail.threads import Thread
|
from rattail.threads import Thread
|
||||||
from rattail.util import simple_error, OrderedDict
|
from rattail.util import simple_error
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
from deform import widget as dfwidget
|
from deform import widget as dfwidget
|
||||||
|
|
|
@ -28,12 +28,13 @@ import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
from rattail.settings import Setting
|
from rattail.settings import Setting
|
||||||
from rattail.util import import_module_path, OrderedDict
|
from rattail.util import import_module_path
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
from sqlalchemy_utils import get_mapper
|
from sqlalchemy_utils import get_mapper
|
||||||
|
|
||||||
from rattail.util import simple_error
|
from rattail.util import simple_error
|
||||||
|
@ -96,8 +97,8 @@ class TableView(MasterView):
|
||||||
where schemaname = 'public'
|
where schemaname = 'public'
|
||||||
order by n_live_tup desc;
|
order by n_live_tup desc;
|
||||||
"""
|
"""
|
||||||
result = self.Session.execute(sql)
|
result = self.Session.execute(sa.text(sql))
|
||||||
return [dict(table_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):
|
||||||
|
|
|
@ -29,6 +29,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
import warnings
|
import warnings
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
@ -36,7 +37,6 @@ from rattail.core import Object
|
||||||
from rattail.db import model, Session as RattailSession
|
from rattail.db import model, Session as RattailSession
|
||||||
from rattail.time import make_utc
|
from rattail.time import make_utc
|
||||||
from rattail.threads import Thread
|
from rattail.threads import Thread
|
||||||
from rattail.util import OrderedDict
|
|
||||||
|
|
||||||
from deform import widget as dfwidget
|
from deform import widget as dfwidget
|
||||||
from webhelpers2.html import tags, HTML
|
from webhelpers2.html import tags, HTML
|
||||||
|
|
Loading…
Reference in a new issue