From 77ea6820f2926be4636974e5a08e13b585d4b305 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 2 Jan 2026 22:06:02 -0600 Subject: [PATCH] customize the installer, to support server and/or terminal for now it's server only though, but laying groundwork.. --- pyproject.toml | 2 +- src/wuttapos/app.py | 15 +- src/wuttapos/cli/run.py | 3 +- src/wuttapos/{config.py => conf.py} | 5 + src/wuttapos/install.py | 78 ++++++++ .../installer-templates/upgrade.sh.mako | 29 +++ .../installer-templates/web.conf.mako | 90 ++++++++++ .../installer-templates/wutta.conf.mako | 168 ++++++++++++++++++ src/wuttapos/server/views/__init__.py | 6 +- src/wuttapos/server/views/common.py | 105 +++++++++++ 10 files changed, 494 insertions(+), 7 deletions(-) rename src/wuttapos/{config.py => conf.py} (93%) create mode 100644 src/wuttapos/install.py create mode 100755 src/wuttapos/installer-templates/upgrade.sh.mako create mode 100644 src/wuttapos/installer-templates/web.conf.mako create mode 100644 src/wuttapos/installer-templates/wutta.conf.mako create mode 100644 src/wuttapos/server/views/common.py diff --git a/pyproject.toml b/pyproject.toml index a7f6141..3cad60a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ terminal = ["flet[all]<0.21"] wuttapos = "wuttapos.app:WuttaPosAppProvider" [project.entry-points."wutta.config.extensions"] -"wuttapos" = "wuttapos.config:WuttaPosConfigExtension" +"wuttapos" = "wuttapos.conf:WuttaPosConfigExtension" # TODO: (why) is this needed again? [project.entry-points."wutta.web.menus"] diff --git a/src/wuttapos/app.py b/src/wuttapos/app.py index a3f9d2e..bc28689 100644 --- a/src/wuttapos/app.py +++ b/src/wuttapos/app.py @@ -27,17 +27,16 @@ WuttaPOS app from wuttjamaican import app as base -class WuttaPosAppProvider(base.AppProvider): +class WuttaPosAppHandler(base.AppHandler): """ - Custom :term:`app provider` for WuttaPOS. + Custom :term:`app handler` for WuttaPOS. """ default_people_handler_spec = "wuttapos.people:PeopleHandler" default_employment_handler_spec = "wuttapos.employment:EmploymentHandler" default_clientele_handler_spec = "wuttapos.clientele:ClienteleHandler" default_products_handler_spec = "wuttapos.products:ProductsHandler" - - email_templates = ["wuttapos:email-templates"] + default_install_handler_spec = "wuttapos.install:InstallHandler" def get_clientele_handler(self): """ @@ -94,3 +93,11 @@ class WuttaPosAppProvider(base.AppProvider): :meth:`~wuttjamaican.people.PeopleHandler.get_person()`. """ return self.get_employment_handler().get_employee(obj) + + +class WuttaPosAppProvider(base.AppProvider): + """ + Custom :term:`app provider` for WuttaPOS. + """ + + email_templates = ["wuttapos:email-templates"] diff --git a/src/wuttapos/cli/run.py b/src/wuttapos/cli/run.py index 867be54..da47109 100644 --- a/src/wuttapos/cli/run.py +++ b/src/wuttapos/cli/run.py @@ -27,7 +27,6 @@ See also: :ref:`wuttapos-run` import typer from wuttapos.cli import wuttapos_typer -from wuttapos.terminal.app import run_app @wuttapos_typer.command() @@ -35,5 +34,7 @@ def run(ctx: typer.Context): """ Run the WuttaPOS GUI app """ + from wuttapos.terminal.app import run_app + config = ctx.parent.wutta_config run_app(config) diff --git a/src/wuttapos/config.py b/src/wuttapos/conf.py similarity index 93% rename from src/wuttapos/config.py rename to src/wuttapos/conf.py index f4902f7..a4876a8 100644 --- a/src/wuttapos/config.py +++ b/src/wuttapos/conf.py @@ -40,6 +40,11 @@ class WuttaPosConfigExtension(WuttaConfigExtension): config.setdefault(f"{config.appname}.app_title", "WuttaPOS") config.setdefault(f"{config.appname}.app_dist", "WuttaPOS") + # app handler + config.setdefault( + f"{config.appname}.app.handler", "wuttapos.app:WuttaPosAppHandler" + ) + # app model config.setdefault(f"{config.appname}.model_spec", "wuttapos.db.model") config.setdefault(f"{config.appname}.enum_spec", "wuttapos.enum") diff --git a/src/wuttapos/install.py b/src/wuttapos/install.py new file mode 100644 index 0000000..d780e1c --- /dev/null +++ b/src/wuttapos/install.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# WuttaPOS -- Point of Sale system based on Wutta Framework +# Copyright © 2026 Lance Edgar +# +# This file is part of WuttaPOS. +# +# WuttaPOS is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# WuttaPOS. If not, see . +# +################################################################################ +""" +Install handler for WuttaPOS +""" + +import subprocess +import sys + +from wuttjamaican import install as base + + +class InstallHandler(base.InstallHandler): + """ + Custom install handler for WuttaPOS + """ + + template_paths = ["wuttapos:installer-templates"] + + def do_install_steps(self): + + # prompt for install type first + self.get_install_type() + + # then everything else + super().do_install_steps() + + def get_install_type(self): + + # prompt user + install_type = None + while install_type not in ("server", "terminal"): + install_type = self.prompt_generic( + "install type (server/terminal)", default="server" + ) + + # remember the answer + self.install_type = install_type + + if self.install_type != "server": + self.rprint( + "[bold red]sorry, terminal install is not yet implemented[/bold red]\n" + ) + sys.exit(1) + + # install dependencies + if self.install_type == "server": + self.install_server_deps() + + def install_server_deps(self): + subprocess.check_call( + [sys.executable, "-m", "pip", "install", "WuttaPOS[server]"] + ) + + def make_template_context(self, dbinfo, **kwargs): + context = super().make_template_context(dbinfo, **kwargs) + context["install_type"] = self.install_type + return context diff --git a/src/wuttapos/installer-templates/upgrade.sh.mako b/src/wuttapos/installer-templates/upgrade.sh.mako new file mode 100755 index 0000000..aadc31a --- /dev/null +++ b/src/wuttapos/installer-templates/upgrade.sh.mako @@ -0,0 +1,29 @@ +#!/bin/sh -e +<%text>################################################## +# +# ${app_title} - upgrade script +# +<%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 '${pypi_name}' + +# migrate schema +$ALEMBIC -c app/wutta.conf upgrade heads diff --git a/src/wuttapos/installer-templates/web.conf.mako b/src/wuttapos/installer-templates/web.conf.mako new file mode 100644 index 0000000..4d2b3c7 --- /dev/null +++ b/src/wuttapos/installer-templates/web.conf.mako @@ -0,0 +1,90 @@ +## -*- mode: conf; -*- + +<%text>############################################################ +# +# ${app_title} - web app config +# +<%text>############################################################ + + +<%text>############################## +# wutta +<%text>############################## + +${self.section_wutta_config()} + + +<%text>############################## +# pyramid +<%text>############################## + +${self.section_app_main()} + +${self.section_server_main()} + + +<%text>############################## +# logging +<%text>############################## + +${self.sectiongroup_logging()} + + +###################################################################### +## section templates below +###################################################################### + +<%def name="section_wutta_config()"> +[wutta.config] +require = %(here)s/wutta.conf + + +<%def name="section_app_main()"> +[app:main] +#use = egg:wuttaweb +use = egg:${egg_name} + +pyramid.reload_templates = true +pyramid.debug_all = true +pyramid.default_locale_name = en +#pyramid.includes = pyramid_debugtoolbar + +beaker.session.type = file +beaker.session.data_dir = %(here)s/cache/sessions/data +beaker.session.lock_dir = %(here)s/cache/sessions/lock +beaker.session.secret = ${beaker_secret} +beaker.session.key = ${beaker_key} + +exclog.extra_info = true + +# required for wuttaweb +wutta.config = %(__file__)s + + +<%def name="section_server_main()"> +[server:main] +use = egg:waitress#main +host = ${pyramid_host} +port = ${pyramid_port} + +# NOTE: this is needed for local reverse proxy stuff to work with HTTPS +# https://docs.pylonsproject.org/projects/waitress/en/latest/reverse-proxy.html +# https://docs.pylonsproject.org/projects/waitress/en/latest/arguments.html +trusted_proxy = 127.0.0.1 +trusted_proxy_headers = x-forwarded-for x-forwarded-host x-forwarded-proto x-forwarded-port +clear_untrusted_proxy_headers = True + +# TODO: leave this empty if proxy serves as root site, e.g. https://wutta.example.com/ +# url_prefix = + +# TODO: or, if proxy serves as subpath of root site, e.g. https://wutta.example.com/backend/ +# url_prefix = /backend + + +<%def name="sectiongroup_logging()"> +[handler_console] +level = INFO + +[handler_file] +args = (${repr(os.path.join(appdir, 'log', 'web.log'))}, 'a', 1000000, 100, 'utf_8') + diff --git a/src/wuttapos/installer-templates/wutta.conf.mako b/src/wuttapos/installer-templates/wutta.conf.mako new file mode 100644 index 0000000..f819a4f --- /dev/null +++ b/src/wuttapos/installer-templates/wutta.conf.mako @@ -0,0 +1,168 @@ +## -*- mode: conf; -*- +##<%inherit file="wuttjamaican:templates/install/wutta.conf.mako" /> + +<%text>############################################################ +# +# ${app_title} - base config +# +<%text>############################################################ + + +<%text>############################## +# wutta +<%text>############################## + +${self.section_wutta()} + +${self.section_wutta_config()} + +${self.section_wutta_db()} + +${self.section_wutta_mail()} + +${self.section_wutta_upgrades()} + + +<%text>############################## +# alembic +<%text>############################## + +${self.section_alembic()} + + +<%text>############################## +# logging +<%text>############################## + +${self.sectiongroup_logging()} + + +###################################################################### +## section templates below +###################################################################### + +<%def name="section_wutta()"> +[wutta] +app_title = ${app_title} + + +<%def name="section_wutta_config()"> +[wutta.config] +#require = /etc/wutta/wutta.conf +configure_logging = true +usedb = true +preferdb = true + + +<%def name="section_wutta_db()"> +[wutta.db] +default.url = ${db_url} + +% if install_type == "server": +[wutta_continuum] +enable_versioning = true +% endif + + +<%def name="section_wutta_mail()"> +[wutta.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 wuttjamaican are used +templates = wuttjamaican:templates/mail + +## TODO +## # 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 = wutta@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 + + +<%def name="section_wutta_upgrades()"> +## TODO +## [wutta.upgrades] +## command = ${os.path.join(appdir, 'upgrade.sh')} --verbose +## files = ${os.path.join(appdir, 'data', 'upgrades')} + + +<%def name="section_alembic()"> +[alembic] +script_location = wuttjamaican.db:alembic +% if install_type == "server": +version_locations = wuttapos.db:alembic/versions wutta_continuum.db:alembic/versions wuttjamaican.db:alembic/versions +% else: +version_locations = wuttapos.db:alembic/versions wuttjamaican.db:alembic/versions +% endif + + +<%def name="sectiongroup_logging()"> +[loggers] +keys = root, beaker, exc_logger, sqlalchemy, txn + +[handlers] +keys = file, console, email + +[formatters] +keys = generic, console + +[logger_root] +handlers = file, console +level = DEBUG + +[logger_beaker] +qualname = beaker +handlers = +level = INFO + +[logger_exc_logger] +qualname = exc_logger +handlers = email +level = ERROR + +[logger_sqlalchemy] +qualname = sqlalchemy.engine +handlers = +# handlers = file +# level = INFO + +[logger_txn] +qualname = txn +handlers = +level = INFO + +[handler_file] +class = handlers.RotatingFileHandler +args = (${repr(os.path.join(appdir, 'log', 'wutta.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', 'wutta@localhost', ['root@localhost'], "[${app_title}] 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 + diff --git a/src/wuttapos/server/views/__init__.py b/src/wuttapos/server/views/__init__.py index b2d5f29..f25671c 100644 --- a/src/wuttapos/server/views/__init__.py +++ b/src/wuttapos/server/views/__init__.py @@ -24,11 +24,15 @@ WuttaPOS server views """ +from wuttaweb.views import essential + def includeme(config): # wuttaweb - config.include("wuttaweb.views.essential") + essential.defaults( + config, **{"wuttaweb.views.common": "wuttapos.server.views.common"} + ) # wuttapos config.include("wuttapos.server.views.stores") diff --git a/src/wuttapos/server/views/common.py b/src/wuttapos/server/views/common.py new file mode 100644 index 0000000..e163b42 --- /dev/null +++ b/src/wuttapos/server/views/common.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# WuttaPOS -- Point of Sale system based on Wutta Framework +# Copyright © 2026 Lance Edgar +# +# This file is part of WuttaPOS. +# +# WuttaPOS is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# WuttaPOS. If not, see . +# +################################################################################ +""" +Common views +""" + +from wuttaweb.views import common as base + + +class CommonView(base.CommonView): + """ + Common views + """ + + def setup_enhance_admin_user(self, user): + """ """ + model = self.app.model + session = self.app.get_session(user) + auth = self.app.get_auth_handler() + + site_admin = session.query(model.Role).filter_by(name="Site Admin").first() + if site_admin: + site_admin_perms = [ + "batch.pos.list", + "batch.pos.view", + "customers.list", + "customers.create", + "customers.view", + "customers.edit", + "departments.list", + "departments.create", + "departments.view", + "departments.edit", + "employees.list", + "employees.create", + "employees.view", + "employees.edit", + "inventory_adjustment_types.list", + "inventory_adjustment_types.create", + "inventory_adjustment_types.view", + "inventory_adjustment_types.edit", + "inventory_adjustments.list", + "inventory_adjustments.create", + "inventory_adjustments.view", + "pos.test_error", + "pos.ring_sales", + "pos.override_price", + "pos.del_customer", + "pos.toggle_training", + "pos.suspend", + "pos.swap_customer", + "pos.void_txn", + "products.list", + "products.create", + "products.view", + "products.edit", + "stores.list", + "stores.create", + "stores.view", + "stores.edit", + "taxes.list", + "taxes.create", + "taxes.view", + "taxes.edit", + "tenders.list", + "tenders.create", + "tenders.view", + "tenders.edit", + "terminals.list", + "terminals.create", + "terminals.view", + "terminals.edit", + ] + for perm in site_admin_perms: + auth.grant_permission(site_admin, perm) + + +def defaults(config, **kwargs): + local = globals() + CommonView = kwargs.get("CommonView", local["CommonView"]) + base.defaults(config, **{"CommonView": CommonView}) + + +def includeme(config): + defaults(config)