Use upstream logic for install command, and some web menus

This commit is contained in:
Lance Edgar 2023-01-17 19:30:35 -06:00
parent 617515a167
commit c66d0ababa
6 changed files with 74 additions and 453 deletions

View file

@ -25,15 +25,10 @@ Messkit commands
""" """
import os import os
import stat
import sys import sys
import subprocess import subprocess
import sqlalchemy as sa
from alembic.util.messaging import obfuscate_url_pw
from rattail import commands from rattail import commands
from rattail.files import resource_path
from messkit import __version__ from messkit import __version__
@ -57,155 +52,61 @@ class Command(commands.Command):
long_description = '' long_description = ''
class Install(commands.Subcommand): class Install(commands.InstallSubcommand):
""" """
Install a Messkit app Install the Messkit app
""" """
name = 'install' name = 'install'
description = __doc__.strip() description = __doc__.strip()
def run(self, args): # nb. these must be explicitly set b/c config is not available
# when running normally, e.g. `messkit -n install`
app_title = "Messkit"
app_package = 'messkit'
app_eggname = 'Messkit'
app_pypiname = 'Messkit'
self.rprint("\n\t[blue]Welcome to Messkit![/blue]") def do_install_steps(self):
self.rprint("\n\tThis tool will install and configure a new app.")
self.rprint("\n\t[italic]NB. You should already have created a new database in PostgreSQL or MySQL.[/italic]")
# continue? # first all normal steps
if not self.basic_prompt("continue?", True, is_bool=True): super(Install, self).do_install_steps()
self.rprint()
sys.exit(0)
# appdir must not yet exist # we also install poser..for now..?
appdir = os.path.join(sys.prefix, 'app') self.install_poser()
if os.path.exists(appdir):
self.rprint("\n\t[bold red]appdir already exists:[/bold red] {}\n".format(appdir))
sys.exit(1)
# get db info def put_settings(self, **kwargs):
dbtype = self.basic_prompt('db type', 'postgresql')
dbhost = self.basic_prompt('db host', 'localhost')
dbport = self.basic_prompt('db port', '3306' if dbtype == 'mysql' else '5432')
dbname = self.basic_prompt('db name', 'messkit')
dbuser = self.basic_prompt('db user', 'rattail')
# get db password rattail = [os.path.join(sys.prefix, 'bin', 'rattail'),
dbpass = None '-c', os.path.join(sys.prefix, 'app', 'silent.conf')]
while not dbpass:
dbpass = self.basic_prompt('db pass', is_password=True)
# test db connection
self.rprint("\n\ttesting db connection... ", end='')
dburl = self.make_db_url(dbtype, dbhost, dbport, dbname, dbuser, dbpass)
error = self.test_db_connection(dburl)
if error:
self.rprint("[bold red]cannot connect![/bold red] ..error was:")
self.rprint("\n{}".format(error))
self.rprint("\n\t[bold yellow]aborting mission[/bold yellow]\n")
sys.exit(1)
self.rprint("[bold green]good[/bold green]")
# make the appdir
self.app.make_appdir(appdir)
# shared context for generated app files
context = {
'envdir': sys.prefix,
'app_package': 'messkit',
'app_title': "Messkit",
'appdir': appdir,
'db_url': dburl,
'pyramid_egg': 'Messkit',
'beaker_key': 'messkit',
}
# make config files
rattail_conf = self.app.make_config_file(
'rattail', os.path.join(appdir, 'rattail.conf'),
template_path=resource_path('messkit:templates/installer/rattail.conf.mako'),
**context)
quiet_conf = self.app.make_config_file('quiet', appdir)
web_conf = self.app.make_config_file(
'web-complete', os.path.join(appdir, 'web.conf'),
**context)
# make upgrade script
path = os.path.join(appdir, 'upgrade.sh')
self.app.render_mako_template(
resource_path('messkit:templates/installer/upgrade.sh.mako'),
context, output_path=path)
os.chmod(path, stat.S_IRWXU
| stat.S_IRGRP
| stat.S_IXGRP
| stat.S_IROTH
| stat.S_IXOTH)
self.rprint("\n\tappdir created at: [bold green]{}[/bold green]".format(appdir))
bindir = os.path.join(sys.prefix, 'bin')
schema_installed = False
if self.basic_prompt("install db schema?", True, is_bool=True):
self.rprint()
# install db schema
alembic = os.path.join(bindir, 'alembic')
cmd = [alembic, '-c', rattail_conf, 'upgrade', 'heads']
subprocess.check_call(cmd)
schema_installed = True
rattail = os.path.join(bindir, 'rattail')
# set falafel theme # set falafel theme
cmd = [rattail, '-c', quiet_conf, '--no-versioning', cmd = rattail + ['setting-put', 'tailbone.theme', 'falafel']
'setting-put', 'tailbone.theme', 'falafel'] subprocess.check_call(cmd)
# hide theme picker
cmd = rattail + ['setting-put', 'tailbone.themes.expose_picker', 'false']
subprocess.check_call(cmd) subprocess.check_call(cmd)
# set main image # set main image
cmd = [rattail, '-c', quiet_conf, '--no-versioning', cmd = rattail + ['setting-put', 'tailbone.main_image_url', '/messkit/img/messkit.png']
'setting-put', 'tailbone.main_image_url', '/messkit/img/messkit.png']
subprocess.check_call(cmd) subprocess.check_call(cmd)
# set header image # set header image
cmd = [rattail, '-c', quiet_conf, '--no-versioning', cmd = rattail + ['setting-put', 'tailbone.header_image_url', '/messkit/img/messkit-small.png']
'setting-put', 'tailbone.header_image_url', '/messkit/img/messkit-small.png']
subprocess.check_call(cmd) subprocess.check_call(cmd)
# set favicon image # set favicon image
cmd = [rattail, '-c', quiet_conf, '--no-versioning', cmd = rattail + ['setting-put', 'tailbone.favicon_url', '/messkit/img/messkit-small.png']
'setting-put', 'tailbone.favicon_url', '/messkit/img/messkit-small.png']
subprocess.check_call(cmd) subprocess.check_call(cmd)
self.rprint("\n\tdb schema installed to: [bold green]{}[/bold green]".format( # set default grid page size
obfuscate_url_pw(dburl))) cmd = rattail + ['setting-put', 'tailbone.grid.default_pagesize', '20']
if self.basic_prompt("create admin user?", True, is_bool=True):
# get admin credentials
username = self.basic_prompt('admin username', 'admin')
password = None
while not password:
password = self.basic_prompt('admin password', is_password=True)
if password:
confirm = self.basic_prompt('confirm password', is_password=True)
if not confirm or confirm != password:
self.rprint("[bold yellow]passwords did not match[/bold yellow]")
password = None
fullname = self.basic_prompt('full name')
self.rprint()
# make admin user
rattail = os.path.join(bindir, 'rattail')
cmd = [rattail, '-c', quiet_conf, 'make-user', '-A', username,
'--password', password]
if fullname:
cmd.extend(['--full-name', fullname])
subprocess.check_call(cmd) subprocess.check_call(cmd)
self.rprint("\n\tadmin user created: [bold green]{}[/bold green]".format( def install_poser(self):
username)) if not self.basic_prompt("make poser dir?", True, is_bool=True):
return False
if self.basic_prompt("make poser dir?", True, is_bool=True):
self.rprint() self.rprint()
# make poser dir # make poser dir
@ -214,46 +115,4 @@ class Install(commands.Subcommand):
self.rprint("\n\tposer dir created: [bold green]{}[/bold green]".format( self.rprint("\n\tposer dir created: [bold green]{}[/bold green]".format(
poserdir)) poserdir))
return True
self.rprint("\n\t[bold green]initial setup is complete![/bold green]")
if schema_installed:
self.rprint("\n\tyou can run the web app with this command:")
pserve = os.path.join(bindir, 'pserve')
self.rprint("\n\t[blue]{} file+ini:{}[/blue]".format(pserve, web_conf))
self.rprint()
# TODO: somewhere should ask about apache proxy, https etc.?
def make_db_url(self, dbtype, dbhost, dbport, dbname, dbuser, dbpass):
try:
# newer style
from sqlalchemy.engine import URL
factory = URL.create
except ImportError:
# older style
from sqlalchemy.engine.url import URL
factory = URL
if dbtype == 'mysql':
drivername = 'mysql+mysqlconnector'
else:
drivername = 'postgresql+psycopg2'
return factory(drivername=drivername,
username=dbuser,
password=dbpass,
host=dbhost,
port=dbport,
database=dbname)
def test_db_connection(self, url):
engine = sa.create_engine(url)
# check for random table; does not matter if it exists, we
# just need to test interaction and this is a neutral way
try:
engine.has_table('whatever')
except Exception as error:
return str(error)

View file

@ -2,7 +2,7 @@
###################################################################### ######################################################################
# #
# Messkit -- Generic-ish Data Utility App # Messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar # Copyright © 2022-2023 Lance Edgar
# #
# This file is part of Messkit. # This file is part of Messkit.
# #
@ -42,7 +42,7 @@ class MesskitConfig(ConfigExtension):
config.setdefault('rattail', 'app_title', "Messkit") config.setdefault('rattail', 'app_title', "Messkit")
config.setdefault('rattail', 'app_class_prefix', 'Messkit') config.setdefault('rattail', 'app_class_prefix', 'Messkit')
config.setdefault('rattail', 'app_table_prefix', 'messkit') config.setdefault('rattail', 'app_table_prefix', 'messkit')
config.setdefault('tailbone', 'menus', 'messkit.web.menus') config.setdefault('tailbone.menus', 'handler', 'messkit.web.menus:MesskitMenuHandler')
config.setdefault('rattail', 'enum', 'messkit.enum') config.setdefault('rattail', 'enum', 'messkit.enum')
config.setdefault('rattail', 'model', 'messkit.db.model') config.setdefault('rattail', 'model', 'messkit.db.model')
config.setdefault('rattail.mail', 'emails', 'messkit.emails') config.setdefault('rattail.mail', 'emails', 'messkit.emails')

View file

@ -1,146 +0,0 @@
## -*- mode: conf; -*-
<%text>############################################################</%text>
#
# ${app_title} core config
#
<%text>############################################################</%text>
<%text>##############################</%text>
# rattail
<%text>##############################</%text>
[rattail]
app_package = ${app_package}
timezone.default = ${timezone}
appdir = ${appdir}
datadir = ${os.path.join(appdir, 'data')}
batch.files = ${os.path.join(appdir, 'data', 'batch')}
workdir = ${os.path.join(appdir, 'work')}
export.files = ${os.path.join(appdir, 'data', 'exports')}
[rattail.config]
# require = /etc/rattail/rattail.conf
configure_logging = true
usedb = true
preferdb = true
[rattail.db]
default.url = ${db_url}
versioning.enabled = true
[rattail.mail]
# this is the global email shutoff switch
#send_emails = false
# recommended setup is to always talk to postfix on localhost and then
# it can handle any need complexities, e.g. sending to relay
smtp.server = localhost
# by default only email templates from rattail proper are used
templates = rattail:templates/mail
# this is the "default" email profile, from which all others initially
# inherit, but most/all profiles will override these values
default.prefix = [${app_title}]
default.from = rattail@localhost
default.to = root@localhost
# nb. in test environment it can be useful to disable by default, and
# then selectively enable certain (e.g. feedback, upgrade) emails
#default.enabled = false
[rattail.upgrades]
command = ${os.path.join(appdir, 'upgrade.sh')} --verbose
files = ${os.path.join(appdir, 'data', 'upgrades')}
<%text>##############################</%text>
# alembic
<%text>##############################</%text>
[alembic]
script_location = rattail.db:alembic
version_locations = rattail.db:alembic/versions
<%text>##############################</%text>
# logging
<%text>##############################</%text>
[loggers]
keys = root, exc_logger, beaker, txn, sqlalchemy, django_db, flufl_bounce, requests
[handlers]
keys = file, console, email
[formatters]
keys = generic, console
[logger_root]
handlers = file, console
level = DEBUG
[logger_exc_logger]
qualname = exc_logger
handlers = email
level = ERROR
[logger_beaker]
qualname = beaker
handlers =
level = INFO
[logger_txn]
qualname = txn
handlers =
level = INFO
[logger_sqlalchemy]
qualname = sqlalchemy.engine
handlers =
# handlers = file
# level = INFO
[logger_django_db]
qualname = django.db.backends
handlers =
level = INFO
# level = DEBUG
[logger_flufl_bounce]
qualname = flufl.bounce
handlers =
level = WARNING
[logger_requests]
qualname = requests
handlers =
# level = WARNING
[handler_file]
class = handlers.RotatingFileHandler
args = (${repr(os.path.join(appdir, 'log', 'rattail.log'))}, 'a', 1000000, 100, 'utf_8')
formatter = generic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
formatter = console
# formatter = generic
# level = INFO
# level = WARNING
[handler_email]
class = handlers.SMTPHandler
args = ('localhost', 'rattail@localhost', ['root@localhost'], "[Rattail] Logging")
formatter = generic
level = ERROR
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(funcName)s: %(message)s
datefmt = %Y-%m-%d %H:%M:%S
[formatter_console]
format = %(levelname)-5.5s [%(name)s][%(threadName)s] %(funcName)s: %(message)s

View file

@ -1,29 +0,0 @@
#!/bin/sh -e
<%text>##################################################</%text>
#
# upgrade script for ${app_title} app
#
<%text>##################################################</%text>
if [ "$1" = "--verbose" ]; then
VERBOSE='--verbose'
QUIET=
else
VERBOSE=
QUIET='--quiet'
fi
cd ${envdir}
PIP='bin/pip'
ALEMBIC='bin/alembic'
# upgrade pip and friends
$PIP install $QUIET --disable-pip-version-check --upgrade pip
$PIP install $QUIET --upgrade setuptools wheel
# upgrade app proper
$PIP install $QUIET --upgrade --upgrade-strategy eager Messkit
# migrate schema
$ALEMBIC -c app/rattail.conf upgrade heads

View file

@ -2,7 +2,7 @@
###################################################################### ######################################################################
# #
# Messkit -- Generic-ish Data Utility App # Messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar # Copyright © 2022-2023 Lance Edgar
# #
# This file is part of Messkit. # This file is part of Messkit.
# #
@ -24,8 +24,15 @@
Web Menus Web Menus
""" """
from tailbone import menus as base
def simple_menus(request):
class MesskitMenuHandler(base.MenuHandler):
"""
Messkit menu handler
"""
def make_menus(self, request, **kwargs):
people_menu = { people_menu = {
'title': "People", 'title': "People",
@ -39,77 +46,9 @@ def simple_menus(request):
], ],
} }
reports_menu = { reports_menu = self.make_reports_menu(request, include_poser=True)
'title': "Reports",
'type': 'menu',
'items': [
{
'title': "New Report",
'route': 'report_output.create',
'perm': 'report_output.create',
},
{
'title': "Generated Reports",
'route': 'report_output',
'perm': 'report_output.list',
},
{
'title': "Problem Reports",
'route': 'problem_reports',
'perm': 'problem_reports.list',
},
{'type': 'sep'},
{
'title': "Poser Reports",
'route': 'poser_reports',
'perm': 'poser_reports.list',
},
],
}
admin_menu = { admin_menu = self.make_admin_menu(request, include_stores=False)
'title': "Admin",
'type': 'menu',
'items': [
{
'title': "Users",
'route': 'users',
'perm': 'users.list',
},
{
'title': "Roles",
'route': 'roles',
'perm': 'roles.list',
},
{'type': 'sep'},
{
'title': "App Settings",
'route': 'appsettings',
'perm': 'settings.list',
},
{
'title': "Email Settings",
'route': 'emailprofiles',
'perm': 'emailprofiles.list',
},
{
'title': "Raw Settings",
'route': 'settings',
'perm': 'settings.list',
},
{'type': 'sep'},
{
'title': "Tables",
'route': 'tables',
'perm': 'tables.list',
},
{
'title': "Messkit Upgrades",
'route': 'upgrades',
'perm': 'upgrades.list',
},
],
}
menus = [ menus = [
people_menu, people_menu,

View file

@ -2,7 +2,7 @@
###################################################################### ######################################################################
# #
# Messkit -- Generic-ish Data Utility App # Messkit -- Generic-ish Data Utility App
# Copyright © 2022 Lance Edgar # Copyright © 2022-2023 Lance Edgar
# #
# This file is part of Messkit. # This file is part of Messkit.
# #
@ -30,8 +30,6 @@ from tailbone.util import include_configured_views
def includeme(config): def includeme(config):
config.include('tailbone.views.essentials') config.include('tailbone.views.essentials')
config.include('tailbone.views.poser') config.include('tailbone.views.poser')
config.include('tailbone.views.reports')
include_configured_views(config) include_configured_views(config)