diff --git a/tailbone/api/common.py b/tailbone/api/common.py
index b82bafd0..6d8e9344 100644
--- a/tailbone/api/common.py
+++ b/tailbone/api/common.py
@@ -24,10 +24,11 @@
Tailbone Web API - "Common" Views
"""
+from collections import OrderedDict
+
import rattail
from rattail.db import model
from rattail.mail import send_email
-from rattail.util import OrderedDict
from cornice import Service
diff --git a/tailbone/forms/core.py b/tailbone/forms/core.py
index 161bfa25..9f30512b 100644
--- a/tailbone/forms/core.py
+++ b/tailbone/forms/core.py
@@ -26,6 +26,7 @@ Forms Core
import json
import logging
+from collections import OrderedDict
import sqlalchemy as sa
from sqlalchemy import orm
@@ -346,6 +347,7 @@ class Form(object):
self.schema = schema
if self.fields is None and self.schema:
self.set_fields([f.name for f in self.schema])
+ self.grouping = None
self.request = request
self.readonly = readonly
self.readonly_fields = set(readonly_fields or [])
@@ -371,6 +373,7 @@ class Form(object):
self.validators = validators or {}
self.required = required or {}
self.helptext = helptext or {}
+ self.dynamic_helptext = {}
self.focus_spec = focus_spec
self.action_url = action_url
self.cancel_url = cancel_url
@@ -404,6 +407,9 @@ class Form(object):
return get_fieldnames(self.request.rattail_config, self.model_class,
columns=True, proxies=True, relations=True)
+ def set_grouping(self, items):
+ self.grouping = OrderedDict(items)
+
def make_renderers(self):
"""
Return a default set of field renderers, based on :attr:`model_class`.
@@ -728,11 +734,15 @@ class Form(object):
"""
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.
"""
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):
"""
@@ -935,7 +945,10 @@ class Form(object):
# TODO: older logic did this only if field was *not*
# readonly, perhaps should add that back..
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
error_messages = self.get_error_messages(field) if field else None
diff --git a/tailbone/grids/filters.py b/tailbone/grids/filters.py
index e4b522f5..26ef4f59 100644
--- a/tailbone/grids/filters.py
+++ b/tailbone/grids/filters.py
@@ -27,11 +27,11 @@ Grid Filters
import re
import datetime
import logging
+from collections import OrderedDict
import sqlalchemy as sa
from rattail.gpc import GPC
-from rattail.util import OrderedDict
from rattail.core import UNSPECIFIED
from rattail.time import localtime, make_utc
from rattail.util import prettify
diff --git a/tailbone/helpers.py b/tailbone/helpers.py
index aeb6aa01..d4065cc5 100644
--- a/tailbone/helpers.py
+++ b/tailbone/helpers.py
@@ -24,15 +24,13 @@
Template Context Helpers
"""
-from __future__ import unicode_literals, absolute_import
-
import os
import datetime
from decimal import Decimal
+from collections import OrderedDict
from rattail.time import localtime, make_utc
-from rattail.util import (pretty_quantity, pretty_hours, hours_as_decimal,
- OrderedDict)
+from rattail.util import pretty_quantity, pretty_hours, hours_as_decimal
from rattail.db.util import maxlen
from webhelpers2.html import *
diff --git a/tailbone/menus.py b/tailbone/menus.py
index 98006c00..9a0ba066 100644
--- a/tailbone/menus.py
+++ b/tailbone/menus.py
@@ -667,11 +667,18 @@ class MenuHandler(GenericHandler):
'route': 'appinfo',
'perm': 'appinfo.list',
},
- {
- 'title': "Label Settings",
- 'route': 'labelprofiles',
- 'perm': 'labelprofiles.list',
- },
+ ])
+
+ if kwargs.get('include_label_settings', False):
+ items.extend([
+ {
+ 'title': "Label Settings",
+ 'route': 'labelprofiles',
+ 'perm': 'labelprofiles.list',
+ },
+ ])
+
+ items.extend([
{
'title': "Raw Settings",
'route': 'settings',
@@ -807,7 +814,7 @@ def make_menu_entry(request, item):
try:
entry['url'] = request.route_url(entry['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['key'] = entry['route']
else:
diff --git a/tailbone/templates/forms/deform_buefy.mako b/tailbone/templates/forms/deform_buefy.mako
index 4ff9c0b5..39633117 100644
--- a/tailbone/templates/forms/deform_buefy.mako
+++ b/tailbone/templates/forms/deform_buefy.mako
@@ -11,10 +11,23 @@
% if form_body is not Undefined and form_body:
${form_body|n}
+ % elif form.grouping:
+ % for group in form.grouping:
+
+ % endfor
% else:
- % for field in form.fields:
- ${form.render_buefy_field(field)}
- % endfor
+ % for field in form.fields:
+ ${form.render_buefy_field(field)}
+ % endfor
% endif
diff --git a/tailbone/templates/generate_project.mako b/tailbone/templates/generate_project.mako
deleted file mode 100644
index f2b67cb3..00000000
--- a/tailbone/templates/generate_project.mako
+++ /dev/null
@@ -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()">
-
-
-
-
-
- ##
-
-
-
-
-
- ${h.form(request.current_route_url(), ref='rattailForm')}
- ${h.csrf_token(request)}
- ${h.hidden('project_type', value='rattail')}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${h.end_form()}
-
-
-
- ${h.form(request.current_route_url(), ref='rattail_integrationForm')}
- ${h.csrf_token(request)}
- ${h.hidden('project_type', value='rattail_integration')}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${h.hidden('slug', **{'v-model': 'rattail_integration.python_project_name'})}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${h.end_form()}
-
-
-
- ${h.form(request.current_route_url(), ref='tailbone_integrationForm')}
- ${h.csrf_token(request)}
- ${h.hidden('project_type', value='tailbone_integration')}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${h.hidden('slug', **{'v-model': 'tailbone_integration.python_project_name'})}
-
-
-
-
-
-
-
-
-
-
- ${h.end_form()}
-
-
-
- ${h.form(request.current_route_url(), ref='byjoveForm')}
- ${h.csrf_token(request)}
- ${h.hidden('project_type', value='byjove')}
-
-
-
-
- ${h.end_form()}
-
-
-
- ${h.form(request.current_route_url(), ref='fabricForm')}
- ${h.csrf_token(request)}
- ${h.hidden('project_type', value='fabric')}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ##
-
-
-
-
-
-
-
- ${h.end_form()}
-
-
-
-
-
- Generate Project
-
-
-
-%def>
-
-<%def name="modify_this_page_vars()">
- ${parent.modify_this_page_vars()}
-
-%def>
-
-
-${parent.body()}
diff --git a/tailbone/templates/generated-projects/create.mako b/tailbone/templates/generated-projects/create.mako
new file mode 100644
index 00000000..32d205a0
--- /dev/null
+++ b/tailbone/templates/generated-projects/create.mako
@@ -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:
+
+
+ ${project_type}
+
+
+
+
+ % endif
+ ${parent.page_content()}
+%def>
+
+
+${parent.body()}
diff --git a/tailbone/views/batch/handheld.py b/tailbone/views/batch/handheld.py
index d4f15ffd..03b9a441 100644
--- a/tailbone/views/batch/handheld.py
+++ b/tailbone/views/batch/handheld.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2022 Lance Edgar
+# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
@@ -24,10 +24,9 @@
Views for handheld batches
"""
-from __future__ import unicode_literals, absolute_import
+from collections import OrderedDict
from rattail.db import model
-from rattail.util import OrderedDict
import colander
from webhelpers2.html import tags
diff --git a/tailbone/views/batch/inventory.py b/tailbone/views/batch/inventory.py
index e13dacca..b41a995e 100644
--- a/tailbone/views/batch/inventory.py
+++ b/tailbone/views/batch/inventory.py
@@ -27,12 +27,13 @@ Views for inventory batches
import re
import decimal
import logging
+from collections import OrderedDict
from rattail import pod
from rattail.db import model
from rattail.db.util import make_full_description
from rattail.gpc import GPC
-from rattail.util import pretty_quantity, OrderedDict
+from rattail.util import pretty_quantity
import colander
from deform import widget as dfwidget
diff --git a/tailbone/views/batch/product.py b/tailbone/views/batch/product.py
index 50b18953..dfe8d890 100644
--- a/tailbone/views/batch/product.py
+++ b/tailbone/views/batch/product.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2020 Lance Edgar
+# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
@@ -24,10 +24,9 @@
Views for generic product batches
"""
-from __future__ import unicode_literals, absolute_import
+from collections import OrderedDict
from rattail.db import model
-from rattail.util import OrderedDict
import colander
from webhelpers2.html import HTML
diff --git a/tailbone/views/common.py b/tailbone/views/common.py
index 6de6bc2b..3882f357 100644
--- a/tailbone/views/common.py
+++ b/tailbone/views/common.py
@@ -25,9 +25,10 @@ Various common views
"""
import os
+from collections import OrderedDict
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 pyramid import httpexceptions
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index 4f0411ac..ed0ed009 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -32,6 +32,7 @@ import getpass
import shutil
import tempfile
import logging
+from collections import OrderedDict
import json
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.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.threads import Thread
from rattail.csvutil import UnicodeDictWriter
@@ -268,17 +269,7 @@ class MasterView(View):
return labels
def get_class_hierarchy(self):
- hierarchy = []
-
- 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
+ return get_class_hierarchy(self.__class__)
def set_row_labels(self, obj):
labels = self.collect_row_labels()
@@ -2215,8 +2206,9 @@ class MasterView(View):
"""
Returns the master view's index URL.
"""
- route = self.get_route_prefix()
- return self.request.route_url(route, **kwargs)
+ if self.listable:
+ route = self.get_route_prefix()
+ return self.request.route_url(route, **kwargs)
# TODO: this should not be class method, if possible
# (pretty sure overriding as instance method works fine)
diff --git a/tailbone/views/people.py b/tailbone/views/people.py
index 9556f66d..3761941a 100644
--- a/tailbone/views/people.py
+++ b/tailbone/views/people.py
@@ -26,6 +26,7 @@ Person Views
import datetime
import logging
+from collections import OrderedDict
import sqlalchemy as sa
from sqlalchemy import orm
@@ -33,7 +34,7 @@ from sqlalchemy import orm
from rattail.db import model, api
from rattail.db.util import maxlen
from rattail.time import localtime
-from rattail.util import OrderedDict, simple_error
+from rattail.util import simple_error
import colander
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
diff --git a/tailbone/views/principal.py b/tailbone/views/principal.py
index 9effd2af..5d477677 100644
--- a/tailbone/views/principal.py
+++ b/tailbone/views/principal.py
@@ -25,9 +25,9 @@
"""
import copy
+from collections import OrderedDict
from rattail.core import Object
-from rattail.util import OrderedDict
from webhelpers2.html import HTML
diff --git a/tailbone/views/products.py b/tailbone/views/products.py
index cc474840..ebec578e 100644
--- a/tailbone/views/products.py
+++ b/tailbone/views/products.py
@@ -26,7 +26,7 @@ Product Views
import re
import logging
-
+from collections import OrderedDict
import humanize
import sqlalchemy as sa
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.threads import Thread
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
import colander
diff --git a/tailbone/views/projects.py b/tailbone/views/projects.py
index 60b531c9..0cfcd349 100644
--- a/tailbone/views/projects.py
+++ b/tailbone/views/projects.py
@@ -24,207 +24,358 @@
Project views
"""
-import os
-import zipfile
-# from collections import OrderedDict
+from collections import OrderedDict
import colander
+from deform import widget as dfwidget
+
+from rattail.projects import PythonProjectGenerator, PoserProjectGenerator
from tailbone import forms
-from tailbone.views import View
+from tailbone.views import MasterView
-class GenerateProject(colander.MappingSchema):
- """
- 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):
+class GeneratedProjectView(MasterView):
"""
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):
- super(GenerateProjectView, self).__init__(request)
- self.project_handler = self.get_handler()
- # TODO: deprecate / remove this
- self.handler = self.project_handler
+ super(GeneratedProjectView, self).__init__(request)
+ self.project_handler = self.get_project_handler()
- def get_handler(self):
- from rattail.projects.handler import RattailProjectHandler
- return RattailProjectHandler(self.rattail_config)
+ def get_project_handler(self):
+ app = self.get_rattail_app()
+ 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([
- # ('has_db', {'prompt': "Does project need its own Rattail DB?",
- # 'type': 'bool'}),
- # ])
+ project_type = self.request.matchdict.get('project_type')
+ if project_type:
+ form = self.make_project_form(project_type)
+ if form.validate(newstyle=True):
+ zipped = self.generate_project(project_type, form)
+ return self.file_response(zipped)
- 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))
+ else: # no 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):
- zipped = self.generate_project(project_type, form)
- 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())
+ # 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"
- return {
+ # 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",
- 'handler': self.handler,
- # 'choices': choices,
- }
+ 'project_type': project_type,
+ 'form': form,
+ })
def generate_project(self, project_type, form):
- options = form.validated
- slug = options['slug']
- path = self.handler.generate_project(project_type, slug, options)
+ context = dict(form.validated)
+ output = self.project_handler.generate_project(project_type,
+ context=context)
+ return self.project_handler.zip_output(output)
- zipped = '{}.zip'.format(path)
- with zipfile.ZipFile(zipped, 'w', zipfile.ZIP_DEFLATED) as z:
- self.zipdir(z, path, slug)
- return zipped
+ def make_project_form(self, project_type):
- def zipdir(self, zipf, path, slug):
- for root, dirs, files in os.walk(path):
- relative_root = os.path.join(slug, root[len(path)+1:])
- for fname in files:
- zipf.write(os.path.join(root, fname),
- arcname=os.path.join(relative_root, fname))
+ # make form
+ schema = self.project_handler.make_project_schema(project_type)
+ form = forms.Form(schema=schema, request=self.request)
+ form.auto_disable = False
+ form.auto_disable_save = False
+ 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
def defaults(cls, config):
- config.add_tailbone_permission('common', 'common.generate_project',
- "Generate new project source code")
- config.add_route('generate_project', '/generate-project')
- config.add_view(cls, route_name='generate_project',
- permission='common.generate_project',
- renderer='/generate_project.mako')
+ cls._defaults(config)
+ cls._generated_project_defaults(config)
+
+ @classmethod
+ def _generated_project_defaults(cls, config):
+ 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):
base = globals()
- GenerateProjectView = kwargs.get('GenerateProjectView', base['GenerateProjectView'])
- GenerateProjectView.defaults(config)
+ GeneratedProjectView = kwargs.get('GeneratedProjectView', base['GeneratedProjectView'])
+ GeneratedProjectView.defaults(config)
def includeme(config):
diff --git a/tailbone/views/purchasing/receiving.py b/tailbone/views/purchasing/receiving.py
index b180a9a7..511f8164 100644
--- a/tailbone/views/purchasing/receiving.py
+++ b/tailbone/views/purchasing/receiving.py
@@ -28,6 +28,7 @@ import os
import re
import decimal
import logging
+from collections import OrderedDict
import humanize
import sqlalchemy as sa
@@ -35,7 +36,7 @@ import sqlalchemy as sa
from rattail import pod
from rattail.db import model, Session as RattailSession
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
import colander
diff --git a/tailbone/views/reports.py b/tailbone/views/reports.py
index d3345b75..5ded5c5f 100644
--- a/tailbone/views/reports.py
+++ b/tailbone/views/reports.py
@@ -29,13 +29,14 @@ import json
import re
import datetime
import logging
+from collections import OrderedDict
import rattail
from rattail.db import model, Session as RattailSession
from rattail.files import resource_path
from rattail.time import localtime
from rattail.threads import Thread
-from rattail.util import simple_error, OrderedDict
+from rattail.util import simple_error
import colander
from deform import widget as dfwidget
diff --git a/tailbone/views/settings.py b/tailbone/views/settings.py
index 5677f579..472ea199 100644
--- a/tailbone/views/settings.py
+++ b/tailbone/views/settings.py
@@ -28,12 +28,13 @@ import os
import re
import subprocess
import sys
+from collections import OrderedDict
import json
from rattail.db import model
from rattail.settings import Setting
-from rattail.util import import_module_path, OrderedDict
+from rattail.util import import_module_path
import colander
diff --git a/tailbone/views/tables.py b/tailbone/views/tables.py
index 75a61086..d4b9ee8b 100644
--- a/tailbone/views/tables.py
+++ b/tailbone/views/tables.py
@@ -28,6 +28,7 @@ import os
import sys
import warnings
+import sqlalchemy as sa
from sqlalchemy_utils import get_mapper
from rattail.util import simple_error
@@ -96,8 +97,8 @@ class TableView(MasterView):
where schemaname = 'public'
order by n_live_tup desc;
"""
- result = self.Session.execute(sql)
- return [dict(table_name=row['relname'], row_count=row['n_live_tup'])
+ result = self.Session.execute(sa.text(sql))
+ return [dict(table_name=row.relname, row_count=row.n_live_tup)
for row in result]
def configure_grid(self, g):
diff --git a/tailbone/views/upgrades.py b/tailbone/views/upgrades.py
index f6df80d3..eddd677c 100644
--- a/tailbone/views/upgrades.py
+++ b/tailbone/views/upgrades.py
@@ -29,6 +29,7 @@ import os
import re
import logging
import warnings
+from collections import OrderedDict
import sqlalchemy as sa
@@ -36,7 +37,6 @@ from rattail.core import Object
from rattail.db import model, Session as RattailSession
from rattail.time import make_utc
from rattail.threads import Thread
-from rattail.util import OrderedDict
from deform import widget as dfwidget
from webhelpers2.html import tags, HTML