Compare commits

...

16 commits

Author SHA1 Message Date
Lance Edgar 87d84f005a build: add release task 2024-11-24 10:35:20 -06:00
Lance Edgar 86abdea9d7 bump: version 0.2.3 → 0.2.4 2024-11-24 10:33:08 -06:00
Lance Edgar 8946121e8a fix: update project links, kallithea -> forgejo 2024-09-14 13:19:35 -05:00
Lance Edgar 59e61357be docs: use markdown for readme file 2024-09-13 18:42:05 -05:00
Lance Edgar f18037b2fb fix: avoid deprecated base class for config extension 2024-08-16 10:14:33 -05:00
Lance Edgar beddcf6987 fix: just use upstream main() for webapi
apparently this demo is not customizing anything and had some old code
which no longer works (auth policy)
2024-07-18 09:37:28 -05:00
Lance Edgar c67494a18c fix: update menu config per wuttaweb 2024-07-14 11:37:36 -05:00
Lance Edgar e10b1fa2fd fix: update config for default app model
per rattail changes
2024-07-13 09:36:40 -05:00
Lance Edgar 19f4f62229 fix: remove unused alembic script
we just use the upstream script now
2024-07-02 09:44:15 -05:00
Lance Edgar 6756899bbb bump: version 0.2.2 → 0.2.3 2024-07-01 11:54:15 -05:00
Lance Edgar cbb5174ac8 fix: use rattail function to create top-level command
share code in case logic ever changes
2024-07-01 11:54:04 -05:00
Lance Edgar 10e79be01a bump: version 0.2.1 → 0.2.2 2024-06-30 12:00:10 -05:00
Lance Edgar e071a9ba25 fix: declare custom static libcache module for tailbone
whoops, missed this in last commit
2024-06-30 11:59:45 -05:00
Lance Edgar ef696b22b0 bump: version 0.2.0 → 0.2.1 2024-06-30 11:56:19 -05:00
Lance Edgar c883e79a2a fix: add butterball libcache via fanstatic
using vue 3.4.31 and oruga 0.8.12
2024-06-30 11:55:21 -05:00
Lance Edgar 5e466be42e fix: add command to purge shopfoo exports 2024-06-16 14:23:41 -05:00
19 changed files with 30180 additions and 140 deletions

View file

@ -5,6 +5,36 @@ All notable changes to rattail will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## v0.2.4 (2024-11-24)
### Fix
- update project links, kallithea -> forgejo
- avoid deprecated base class for config extension
- just use upstream `main()` for webapi
- update menu config per wuttaweb
- update config for default app model
- remove unused alembic script
## v0.2.3 (2024-07-01)
### Fix
- use rattail function to create top-level command
## v0.2.2 (2024-06-30)
### Fix
- declare custom static libcache module for tailbone
## v0.2.1 (2024-06-30)
### Fix
- add butterball libcache via fanstatic
- add command to purge shopfoo exports
## v0.2.0 (2024-06-10)
### Feat

View file

@ -1,11 +1,9 @@
Rattail Demo
============
# Rattail Demo
This project serves as a working demo, to illustrate various concepts of the
Rattail software framework. See the `Rattail Wiki`_ for more info.
This project serves as a working demo, to illustrate various concepts
of the Rattail software framework. See the [Rattail
Wiki](https://rattailproject.org/moin/) for more info.
Note that it *can be* usable as a starting point for your own project(s),
should you need one. But probably the Rattail Tutorial is a better one.
.. _`Rattail Wiki`: https://rattailproject.org/moin/

View file

@ -6,9 +6,9 @@ build-backend = "hatchling.build"
[project]
name = "rattail-demo"
version = "0.2.0"
version = "0.2.4"
description = "Rattail Software Demo"
readme = "README.rst"
readme = "README.md"
authors = [{name = "Lance Edgar", email = "lance@edbob.org"}]
classifiers = [
"Development Status :: 3 - Alpha",
@ -36,6 +36,10 @@ dependencies = [
rattail-demo = "rattail_demo.commands:rattail_demo_typer"
[project.entry-points."fanstatic.libraries"]
rattail_demo_libcache = "rattail_demo.web.static:libcache"
[project.entry-points."paste.app_factory"]
main = "rattail_demo.web.app:main"
webapi = "rattail_demo.web.webapi:main"
@ -47,8 +51,8 @@ rattail-demo = "rattail_demo.config:DemoConfigExtension"
[project.urls]
Homepage = "https://demo.rattailproject.org"
Repository = "https://kallithea.rattailproject.org/rattail-project/rattail-demo"
Changelog = "https://kallithea.rattailproject.org/rattail-project/rattail-demo/files/master/CHANGELOG.md"
Repository = "https://forgejo.wuttaproject.org/rattail/rattail-demo"
Changelog = "https://forgejo.wuttaproject.org/rattail/rattail-demo/src/branch/master/CHANGELOG.md"
[tool.commitizen]

View file

@ -3,16 +3,25 @@
Rattail Demo Commands
"""
import typer
import datetime
import logging
import os
import shutil
from rattail.commands.typer import (typer_callback, typer_get_runas_user,
import typer
from typing_extensions import Annotated
from rattail.commands.typer import (make_typer, typer_get_runas_user,
importer_command, file_exporter_command)
from rattail.commands.importing import ImportCommandHandler
from rattail.commands.purging import run_purge
# nb. this is the top-level command for titeship
rattail_demo_typer = typer.Typer(
callback=typer_callback,
log = logging.getLogger(__name__)
# nb. this is the top-level command
rattail_demo_typer = make_typer(
name='rattail_demo',
help="Rattail Demo (custom Rattail system)"
)
@ -52,6 +61,67 @@ def import_self(
handler.run(kwargs, progress=progress)
@rattail_demo_typer.command()
def purge_shopfoo(
ctx: typer.Context,
before: Annotated[
datetime.datetime,
typer.Option(formats=['%Y-%m-%d'],
help="Use this date as cutoff, i.e. purge all data "
"*before* this date. If not specified, will use "
"--before-days to calculate instead.")] = None,
before_days: Annotated[
int,
typer.Option(help="Calculate the cutoff date by subtracting this "
"number of days from the current date, i.e. purge all "
"data *before* the resulting date. Note that if you "
"specify --before then that date will be used instead "
"of calculating one from --before-days. If neither is "
"specified then --before-days is used, with its default "
"value.")] = 90,
dry_run: Annotated[
bool,
typer.Option('--dry-run',
help="Go through the full motions and allow logging "
"etc. to occur, but rollback (abort) the transaction "
"at the end.")] = False,
):
"""
Purge old Shopfoo export data
"""
config = ctx.parent.rattail_config
progress = ctx.parent.rattail_progress
app = config.get_app()
model = app.model
def finder(session, cutoff, dry_run=False):
return session.query(model.ShopfooProductExport)\
.filter(model.ShopfooProductExport.created < app.make_utc(cutoff))\
.all()
def purger(session, export, cutoff, dry_run=False):
uuid = export.uuid
log.debug("purging export object %s: %s", uuid, export)
session.delete(export)
# maybe delete associated files
if not dry_run:
session.flush()
key = model.ShopfooProductExport.export_key
path = config.export_filepath(key, uuid)
if os.path.exists(path):
shutil.rmtree(path)
return True
run_purge(config, "Shopfoo Export", "Shopfoo Exports",
finder, purger,
before=before.date() if before else None,
before_days=before_days,
default_before_days=90,
dry_run=dry_run, progress=progress)
@rattail_demo_typer.command()
def install(
ctx: typer.Context,

View file

@ -3,10 +3,10 @@
Rattail Demo config extension
"""
from rattail.config import ConfigExtension
from wuttjamaican.conf import WuttaConfigExtension
class DemoConfigExtension(ConfigExtension):
class DemoConfigExtension(WuttaConfigExtension):
"""
Rattail Demo config extension
"""
@ -17,9 +17,12 @@ class DemoConfigExtension(ConfigExtension):
config.setdefault('rattail', 'app_package', 'rattail_demo')
# tell rattail where our stuff lives
config.setdefault('rattail', 'model', 'rattail_demo.db.model')
config.setdefault('rattail', 'model_spec', 'rattail_demo.db.model')
config.setdefault('rattail.trainwreck', 'model', 'rattail.trainwreck.db.model.defaults')
config.setdefault('tailbone.menus', 'handler', 'rattail_demo.web.menus:DemoMenuHandler')
config.setdefault('tailbone.static_libcache.module', 'rattail_demo.web.static')
# menus
config.setdefault('rattail.web.menus.handler_spec', 'rattail_demo.web.menus:DemoMenuHandler')
# default app handlers
config.setdefault('rattail', 'products.handler', 'rattail_corepos.products:CoreProductsHandler')

View file

@ -1,74 +0,0 @@
# -*- coding: utf-8; mode: python; -*-
"""
Alembic environment script
"""
from alembic import context
from sqlalchemy.orm import configure_mappers
from rattail.config import make_config
from rattail.db.util import get_default_engine
from rattail.db.continuum import configure_versioning
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
alembic_config = context.config
# use same config file for Rattail
rattail_config = make_config(alembic_config.config_file_name, usedb=False, versioning=False)
# configure Continuum..this is trickier than we want but it works..
configure_versioning(rattail_config, force=True)
from rattail_demo.db import model
configure_mappers()
# needed for 'autogenerate' support
target_metadata = model.Base.metadata
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
engine = get_default_engine(rattail_config)
context.configure(
url=engine.url,
target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
engine = get_default_engine(rattail_config)
connection = engine.connect()
context.configure(
connection=connection,
target_metadata=target_metadata)
try:
with context.begin_transaction():
context.run_migrations()
finally:
connection.close()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View file

@ -28,8 +28,8 @@ def main(global_config, **settings):
CoreOfficeSession.configure(bind=rattail_config.corepos_engine)
CoreTransSession.configure(bind=rattail_config.coretrans_engine)
# bring in rest of rattail-demo etc.
pyramid_config.include('tailbone.static')
# bring in rest of rattail-demo
pyramid_config.include('rattail_demo.web.static')
pyramid_config.include('rattail_demo.web.subscribers')
pyramid_config.include('rattail_demo.web.views')

View file

@ -7,7 +7,7 @@ from tailbone import menus as base
from tailbone_corepos.menus import make_corepos_menu
class DemoMenuHandler(base.MenuHandler):
class DemoMenuHandler(base.TailboneMenuHandler):
"""
Demo menu handler
"""
@ -62,7 +62,7 @@ class DemoMenuHandler(base.MenuHandler):
},
{
'title': "Source Code",
'url': 'https://kallithea.rattailproject.org/rattail-project/rattail-demo',
'url': 'https://forgejo.wuttaproject.org/rattail/rattail-demo',
'target': '_blank',
},
{

View file

@ -0,0 +1,22 @@
# -*- coding: utf-8; -*-
"""
Static assets
"""
from fanstatic import Library, Resource
# libcache
libcache = Library('rattail_demo_libcache', 'libcache')
bb_vue_js = Resource(libcache, 'vue.esm-browser-3.4.31.prod.js')
bb_oruga_js = Resource(libcache, 'oruga-0.8.12.js')
bb_oruga_bulma_js = Resource(libcache, 'oruga-bulma-0.3.0.js')
bb_oruga_bulma_css = Resource(libcache, 'oruga-bulma-0.3.0.css')
bb_fontawesome_svg_core_js = Resource(libcache, 'fontawesome-svg-core-6.5.2.js')
bb_free_solid_svg_icons_js = Resource(libcache, 'free-solid-svg-icons-6.5.2.js')
bb_vue_fontawesome_js = Resource(libcache, 'vue-fontawesome-3.0.6.index.es.js')
def includeme(config):
config.include('tailbone.static')
config.add_static_view('rattail_demo', 'rattail_demo.web:static', cache_max_age=3600)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,478 @@
const bulmaConfig = {
field: {
override: true,
rootClass: "field",
labelClass: "label",
labelSizeClass: "is-",
messageClass: "help",
variantMessageClass: "is-",
addonsClass: "has-addons",
groupedClass: "is-grouped",
groupMultilineClass: "is-grouped-multiline",
horizontalClass: "is-horizontal",
labelHorizontalClass: "field-label",
bodyHorizontalClass: "field-body",
bodyClass: "control",
},
input: {
override: true,
rootClass: (_, { props }) => {
const classes = ["control"];
if (props.icon)
classes.push("has-icons-left");
return classes.join(" ").trim();
},
inputClass: "input",
textareaClass: "textarea",
roundedClass: "is-rounded",
variantClass: "is-",
sizeClass: "is-",
expandedClass: "is-expanded",
iconLeftClass: "is-left",
iconRightClass: "is-right",
counterClass: "help counter",
hasIconRightClass: "has-icons-right",
},
select: {
override: true,
rootClass: (_, { props }) => {
const classes = ["control", "select"];
if (props.size)
classes.push(`is-${props.size}`);
if (props.rounded)
classes.push("is-rounded");
if (props.multiple)
classes.push("is-multiple");
if (props.icon)
classes.push("has-icons-left");
if (props.iconRight)
classes.push("has-icons-right");
return classes.join(" ").trim();
},
expandedClass: "is-fullwidth",
iconLeftClass: "is-left",
iconRightClass: "is-right",
placeholderClass: "is-empty",
rootVariantClass: "is-",
},
icon: {
override: true,
rootClass: "icon",
variantClass: "has-text-",
sizeClass: "is-",
clickableClass: "is-clickable",
spinClass: "is-spin",
},
checkbox: {
override: true,
rootClass: "b-checkbox checkbox",
disabledClass: "is-disabled",
inputClass: "check",
labelClass: "control-label",
variantClass: "is-",
sizeClass: "is-",
},
radio: {
override: true,
rootClass: "b-radio radio",
disabledClass: "is-disabled",
inputClass: "check",
labelClass: "control-label",
variantClass: "is-",
sizeClass: "is-",
},
switch: {
override: true,
rootClass: (_, { props }) => {
const classes = ["switch"];
if (props.rounded)
classes.push("is-rounded");
if (props.position === "left")
classes.push("has-left-label");
return classes.join(" ");
},
switchClass: (_, { props }) => {
const classes = ["check"];
if (props.variant)
classes.push(`is-${props.variant}`);
if (props.passiveVariant)
classes.push(`is-${props.passiveVariant}-passive`);
return classes.join(" ");
},
labelClass: "control-label",
sizeClass: "is-",
disabledClass: "is-disabled",
},
autocomplete: {
override: true,
rootClass: "autocomplete control",
itemClass: "dropdown-item",
itemHoverClass: "is-hovered",
itemEmptyClass: "is-disabled",
itemGroupTitleClass: "has-text-weight-bold",
},
taginput: {
override: true,
rootClass: "taginput control",
containerClass: "taginput-container is-focusable",
itemClass: "tag",
closeClass: "delete is-small",
},
pagination: {
override: true,
rootClass: (_, { props }) => {
const classes = ["pagination"];
if (props.rounded)
classes.push("is-rounded");
return classes.join(" ");
},
sizeClass: "is-",
simpleClass: "is-simple",
orderClass: "is-",
listClass: "pagination-list",
linkClass: "pagination-link",
linkCurrentClass: "is-current",
linkDisabledClass: "is-disabled",
nextButtonClass: "pagination-next",
prevButtonClass: "pagination-previous",
infoClass: "info",
},
slider: {
override: true,
rootClass: (_, { props }) => {
const classes = ["b-slider"];
if (props.variant)
classes.push(`is-${props.variant}`);
if (props.rounded)
classes.push("is-rounded");
return classes.join(" ");
},
disabledClass: "is-disabled",
trackClass: "b-slider-track",
fillClass: "b-slider-fill",
thumbWrapperClass: "b-slider-thumb-wrapper",
thumbWrapperDraggingClass: "is-dragging",
sizeClass: "is-",
thumbClass: "b-slider-thumb",
tickLabelClass: "b-slider-tick-label",
tickHiddenClass: "is-tick-hidden",
tickClass: "b-slider-tick",
},
tabs: {
override: true,
itemTag: "a",
rootClass: "b-tabs",
contentClass: "tab-content",
multilineClass: "is-multiline",
navTabsClass: (_, { props }) => {
const classes = ["tabs"];
if (props.type)
classes.push(`is-${props.type}`);
return classes.join(" ");
},
expandedClass: "is-fullwidth",
verticalClass: "is-vertical",
positionClass: "is-",
navSizeClass: "is-",
navPositionClass: "is-",
transitioningClass: "is-transitioning",
itemClass: "tab-item",
itemHeaderActiveClass: () => "is-active",
itemHeaderDisabledClass: () => "is-disabled",
},
table: {
override: true,
rootClass: "b-table",
wrapperClass: "table-wrapper",
tableClass: "table",
borderedClass: "is-bordered",
stripedClass: "is-striped",
narrowedClass: "is-narrow",
hoverableClass: "is-hoverable",
emptyClass: "is-empty",
detailedClass: "detail",
footerClass: "table-footer",
paginationWrapperClass: "level",
scrollableClass: "table-container",
stickyHeaderClass: "has-sticky-header",
trSelectedClass: "is-selected",
thSortableClass: "is-sortable",
thCurrentSortClass: "is-current-sort",
thSortIconClass: "th-wrap sort-icon",
thUnselectableClass: "is-unselectable",
thStickyClass: "is-sticky",
thCheckboxClass: "th-checkbox",
thDetailedClass: "th-chevron-cell",
tdDetailedChevronClass: "chevron-cell",
thPositionClass: (position) => {
if (position === "centered")
return "is-centered";
else if (position === "right")
return "is-right";
return;
},
tdPositionClass: (position) => {
if (position === "centered")
return "has-text-centered";
else if (position === "right")
return "has-text-right";
return;
},
mobileClass: "is-mobile",
mobileSortClass: "table-mobile-sort field",
},
tooltip: {
override: true,
rootClass: (_, { props }) => {
const classes = ["b-tooltip"];
if (props.variant)
classes.push(`is-${props.variant}`);
else
classes.push(`is-primary`);
return classes.join(" ");
},
contentClass: "b-tooltip-content",
triggerClass: "b-tooltip-trigger",
alwaysClass: "is-always",
multilineClass: "is-multiline",
variantClass: "is-",
positionClass: "is-",
},
steps: {
override: true,
rootClass: (_, { props }) => {
const classes = ["b-steps"];
if (props.variant)
classes.push(`is-${props.variant}`);
if (props.disables)
classes.push("is-disabled");
return classes.join(" ");
},
stepsClass: (_, { props }) => {
const classes = ["steps"];
if (props.animated)
classes.push("is-animated");
if (props.rounded)
classes.push("is-rounded");
if (props.labelPosition === "left")
classes.push("has-label-left");
if (props.labelPosition === "right")
classes.push("has-label-right");
return classes.join(" ");
},
itemClass: "step-link",
itemHeaderClass: "step-item",
itemHeaderVariantClass: "is-",
itemHeaderActiveClass: "is-active",
itemHeaderPreviousClass: "is-previous",
stepLinkClass: "step-link",
stepLinkLabelClass: "step-title",
stepLinkClickableClass: "is-clickable",
stepMarkerClass: "step-marker",
stepNavigationClass: "step-navigation",
stepContentClass: "step-content",
verticalClass: "is-vertical",
positionClass: "is-",
stepContentTransitioningClass: "is-transitioning",
sizeClass: "is-",
},
button: {
override: true,
rootClass: "button",
sizeClass: "is-",
variantClass: "is-",
roundedClass: "is-rounded",
expandedClass: "is-fullwidth",
loadingClass: "is-loading",
outlinedClass: () => "is-outlined",
invertedClass: () => "is-inverted",
wrapperClass: "button-wrapper",
},
menu: {
override: true,
rootClass: "menu",
listClass: "menu-list",
listLabelClass: "menu-label",
},
skeleton: {
override: true,
rootClass: (_, { props }) => {
const classes = ["b-skeleton"];
if (props.animated)
classes.push("is-animated");
return classes.join(" ");
},
itemClass: "b-skeleton-item",
itemRoundedClass: "is-rounded",
},
notification: {
override: true,
rootClass: (_, { props }) => {
const classes = ["notification"];
if (props.variant)
classes.push(`is-${props.variant}`);
return classes.join(" ");
},
wrapperClass: "media",
contentClass: "media-content",
iconClass: "media-left",
closeClass: "delete",
positionClass: "is-",
noticeClass: "b-notices",
noticePositionClass: "is-",
variantClass: "is-",
},
dropdown: {
override: true,
itemTag: "a",
rootClass: ["dropdown", "dropdown-menu-animation"],
triggerClass: "dropdown-trigger",
menuClass: "dropdown-content dropdown-menu",
disabledClass: "is-disabled",
expandedClass: "is-expanded",
inlineClass: "is-inline",
itemClass: "dropdown-item",
itemActiveClass: "is-active",
itemDisabledClass: "is-disabled",
mobileClass: "is-mobile-modal",
menuMobileOverlayClass: "background",
positionClass: "is-",
activeClass: "is-active",
hoverableClass: "is-hoverable",
position: "bottom-right",
},
datepicker: {
override: true,
rootClass: "datepicker",
headerClass: "datepicker-header",
footerClass: "datepicker-footer",
boxClass: "dropdown-item",
tableClass: "datepicker-table",
tableHeadClass: "datepicker-header",
tableHeadCellClass: "datepicker-cell",
headerButtonsClass: "pagination field is-centered",
prevButtonClass: "pagination-previous",
nextButtonClass: "pagination-next",
listsClass: "pagination-list",
tableBodyClass: (_, { props }) => {
const classes = ["datepicker-body"];
if (props.events)
classes.push(`has-events`);
return classes.join(" ");
},
tableRowClass: "datepicker-row",
tableCellClass: "datepicker-cell",
tableCellSelectableClass: "is-selectable",
tableCellUnselectableClass: "is-unselectable",
tableCellTodayClass: "is-today",
tableCellSelectedClass: "is-selected",
tableCellWithinHoveredClass: "is-within-hovered",
tableCellFirstHoveredClass: "is-first-hovered",
tableCellLastHoveredClass: "is-last-hovered",
tableCellFirstSelectedClass: "is-first-selected",
tableCellLastSelectedClass: "is-last-selected",
tableCellWithinSelectedClass: "is-within-selected",
tableCellInvisibleClass: "",
tableCellNearbyClass: "is-nearby",
tableCellEventsClass: (_, { props }) => {
const classes = ["has-event"];
if (props.indicators)
classes.push(`${props.indicators}`);
return classes.join(" ");
},
tableEventVariantClass: "is-",
tableEventsClass: "events",
tableEventClass: "event",
monthBodyClass: "datepicker-body",
monthCellClass: "datepicker-cell",
monthCellFirstHoveredClass: "is-first-hovered",
monthCellFirstSelectedClass: "is-first-selected",
monthCellLastHoveredClass: "is-last-hovered",
monthCellLastSelectedClass: "is-last-selected",
monthCellSelectableClass: "is-selectable",
monthCellSelectedClass: "is-selected",
monthCellTodayClass: "is-today",
monthCellUnselectableClass: "is-unselectable",
monthCellWithinHoveredClass: "is-within-hovered",
monthCellWithinSelectedClass: "is-within-selected",
monthClass: "datepicker-table",
monthTableClass: "datepicker-months",
},
modal: {
override: true,
rootClass: "modal",
activeClass: "is-active",
overlayClass: "modal-background",
contentClass: "modal-content animation-content",
closeClass: "modal-close is-large",
fullScreenClass: "is-full-screen",
scrollClipClass: "is-clipped",
},
sidebar: {
override: true,
rootClass: "b-sidebar",
variantClass: "is-",
positionClass: "is-",
activeClass: "is-active",
contentClass: "sidebar-content is-fixed",
expandOnHoverClass: "is-mini-expand",
fullheightClass: "is-fullheight",
fullwidthClass: "is-fullwidth",
mobileClass: (_, { props }) => {
if (props.mobile && props.mobile !== "reduce") {
return `is-${props.mobile}-mobile`;
}
},
overlayClass: "sidebar-background",
reduceClass: "is-mini-mobile",
},
loading: {
fullPageClass: "is-full-page",
overlayClass: "loading-overlay",
iconClass: "icon",
rootClass: "loading",
},
timepicker: {
override: true,
rootClass: "timepicker control",
boxClass: "dropdown-item",
selectClasses: {
rootClass: "select control",
},
separatorClass: "is-colon control",
footerClass: "timepicker-footer",
sizeClass: "is-",
},
carousel: {
override: true,
rootClass: "carousel",
overlayClass: "is-overlay",
wrapperClass: "carousel-scene",
itemsClass: "carousel-items",
itemsDraggingClass: "is-dragging",
arrowIconClass: "carousel-arrow",
arrowIconPrevClass: "has-icons-left",
arrowIconNextClass: "has-icons-right",
indicatorsClass: "carousel-indicator",
indicatorClass: "indicator-item",
indicatorsInsideClass: "is-inside",
indicatorsInsidePositionClass: "is-",
indicatorItemClass: "indicator-style",
indicatorItemActiveClass: "is-active",
indicatorItemStyleClass: "is-",
// CarouselItem
itemClass: "carousel-item",
itemActiveClass: "is-active",
},
upload: {
override: true,
rootClass: "upload control",
draggableClass: "upload-draggable",
variantClass: "is-",
expandedClass: "is-expanded",
disabledClass: "is-disabled",
hoveredClass: "is-hovered",
},
};
export { bulmaConfig };

View file

@ -0,0 +1,626 @@
import { parse, icon, config, text } from '@fortawesome/fontawesome-svg-core';
import { h, defineComponent, computed, watch } from 'vue';
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
enumerableOnly && (symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
})), keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = null != arguments[i] ? arguments[i] : {};
i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
_defineProperty(target, key, source[key]);
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
return target;
}
function _typeof(obj) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
}, _typeof(obj);
}
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = _objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}
return target;
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _toPrimitive(input, hint) {
if (typeof input !== "object" || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || "default");
if (typeof res !== "object") return res;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return (hint === "string" ? String : Number)(input);
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, "string");
return typeof key === "symbol" ? key : String(key);
}
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
var humps$1 = {exports: {}};
(function (module) {
(function(global) {
var _processKeys = function(convert, obj, options) {
if(!_isObject(obj) || _isDate(obj) || _isRegExp(obj) || _isBoolean(obj) || _isFunction(obj)) {
return obj;
}
var output,
i = 0,
l = 0;
if(_isArray(obj)) {
output = [];
for(l=obj.length; i<l; i++) {
output.push(_processKeys(convert, obj[i], options));
}
}
else {
output = {};
for(var key in obj) {
if(Object.prototype.hasOwnProperty.call(obj, key)) {
output[convert(key, options)] = _processKeys(convert, obj[key], options);
}
}
}
return output;
};
// String conversion methods
var separateWords = function(string, options) {
options = options || {};
var separator = options.separator || '_';
var split = options.split || /(?=[A-Z])/;
return string.split(split).join(separator);
};
var camelize = function(string) {
if (_isNumerical(string)) {
return string;
}
string = string.replace(/[\-_\s]+(.)?/g, function(match, chr) {
return chr ? chr.toUpperCase() : '';
});
// Ensure 1st char is always lowercase
return string.substr(0, 1).toLowerCase() + string.substr(1);
};
var pascalize = function(string) {
var camelized = camelize(string);
// Ensure 1st char is always uppercase
return camelized.substr(0, 1).toUpperCase() + camelized.substr(1);
};
var decamelize = function(string, options) {
return separateWords(string, options).toLowerCase();
};
// Utilities
// Taken from Underscore.js
var toString = Object.prototype.toString;
var _isFunction = function(obj) {
return typeof(obj) === 'function';
};
var _isObject = function(obj) {
return obj === Object(obj);
};
var _isArray = function(obj) {
return toString.call(obj) == '[object Array]';
};
var _isDate = function(obj) {
return toString.call(obj) == '[object Date]';
};
var _isRegExp = function(obj) {
return toString.call(obj) == '[object RegExp]';
};
var _isBoolean = function(obj) {
return toString.call(obj) == '[object Boolean]';
};
// Performant way to determine if obj coerces to a number
var _isNumerical = function(obj) {
obj = obj - 0;
return obj === obj;
};
// Sets up function which handles processing keys
// allowing the convert function to be modified by a callback
var _processor = function(convert, options) {
var callback = options && 'process' in options ? options.process : options;
if(typeof(callback) !== 'function') {
return convert;
}
return function(string, options) {
return callback(string, convert, options);
}
};
var humps = {
camelize: camelize,
decamelize: decamelize,
pascalize: pascalize,
depascalize: decamelize,
camelizeKeys: function(object, options) {
return _processKeys(_processor(camelize, options), object);
},
decamelizeKeys: function(object, options) {
return _processKeys(_processor(decamelize, options), object, options);
},
pascalizeKeys: function(object, options) {
return _processKeys(_processor(pascalize, options), object);
},
depascalizeKeys: function () {
return this.decamelizeKeys.apply(this, arguments);
}
};
if (module.exports) {
module.exports = humps;
} else {
global.humps = humps;
}
})(commonjsGlobal);
} (humps$1));
var humps = humps$1.exports;
var _excluded = ["class", "style"];
/**
* Converts a CSS style into a plain Javascript object.
* @param {String} style The style to converts into a plain Javascript object.
* @returns {Object}
*/
function styleToObject(style) {
return style.split(';').map(function (s) {
return s.trim();
}).filter(function (s) {
return s;
}).reduce(function (output, pair) {
var idx = pair.indexOf(':');
var prop = humps.camelize(pair.slice(0, idx));
var value = pair.slice(idx + 1).trim();
output[prop] = value;
return output;
}, {});
}
/**
* Converts a CSS class list into a plain Javascript object.
* @param {Array<String>} classes The class list to convert.
* @returns {Object}
*/
function classToObject(classes) {
return classes.split(/\s+/).reduce(function (output, className) {
output[className] = true;
return output;
}, {});
}
/**
* Converts a FontAwesome abstract element of an icon into a Vue VNode.
* @param {AbstractElement | String} abstractElement The element to convert.
* @param {Object} props The user-defined props.
* @param {Object} attrs The user-defined native HTML attributes.
* @returns {VNode}
*/
function convert(abstractElement) {
var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var attrs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
// If the abstract element is a string, we'll just return a string render function
if (typeof abstractElement === 'string') {
return abstractElement;
}
// Converting abstract element children into Vue VNodes
var children = (abstractElement.children || []).map(function (child) {
return convert(child);
});
// Converting abstract element attributes into valid Vue format
var mixins = Object.keys(abstractElement.attributes || {}).reduce(function (mixins, key) {
var value = abstractElement.attributes[key];
switch (key) {
case 'class':
mixins.class = classToObject(value);
break;
case 'style':
mixins.style = styleToObject(value);
break;
default:
mixins.attrs[key] = value;
}
return mixins;
}, {
attrs: {},
class: {},
style: {}
});
// Now, we'll return the VNode
attrs.class;
var _attrs$style = attrs.style,
aStyle = _attrs$style === void 0 ? {} : _attrs$style,
otherAttrs = _objectWithoutProperties(attrs, _excluded);
return h(abstractElement.tag, _objectSpread2(_objectSpread2(_objectSpread2({}, props), {}, {
class: mixins.class,
style: _objectSpread2(_objectSpread2({}, mixins.style), aStyle)
}, mixins.attrs), otherAttrs), children);
}
var PRODUCTION = false;
try {
PRODUCTION = process.env.NODE_ENV === 'production';
} catch (e) {}
function log () {
if (!PRODUCTION && console && typeof console.error === 'function') {
var _console;
(_console = console).error.apply(_console, arguments);
}
}
function objectWithKey(key, value) {
return Array.isArray(value) && value.length > 0 || !Array.isArray(value) && value ? _defineProperty({}, key, value) : {};
}
function classList(props) {
var _classes;
var classes = (_classes = {
'fa-spin': props.spin,
'fa-pulse': props.pulse,
'fa-fw': props.fixedWidth,
'fa-border': props.border,
'fa-li': props.listItem,
'fa-inverse': props.inverse,
'fa-flip': props.flip === true,
'fa-flip-horizontal': props.flip === 'horizontal' || props.flip === 'both',
'fa-flip-vertical': props.flip === 'vertical' || props.flip === 'both'
}, _defineProperty(_classes, "fa-".concat(props.size), props.size !== null), _defineProperty(_classes, "fa-rotate-".concat(props.rotation), props.rotation !== null), _defineProperty(_classes, "fa-pull-".concat(props.pull), props.pull !== null), _defineProperty(_classes, 'fa-swap-opacity', props.swapOpacity), _defineProperty(_classes, 'fa-bounce', props.bounce), _defineProperty(_classes, 'fa-shake', props.shake), _defineProperty(_classes, 'fa-beat', props.beat), _defineProperty(_classes, 'fa-fade', props.fade), _defineProperty(_classes, 'fa-beat-fade', props.beatFade), _defineProperty(_classes, 'fa-flash', props.flash), _defineProperty(_classes, 'fa-spin-pulse', props.spinPulse), _defineProperty(_classes, 'fa-spin-reverse', props.spinReverse), _classes);
return Object.keys(classes).map(function (key) {
return classes[key] ? key : null;
}).filter(function (key) {
return key;
});
}
function normalizeIconArgs(icon) {
if (icon && _typeof(icon) === 'object' && icon.prefix && icon.iconName && icon.icon) {
return icon;
}
if (parse.icon) {
return parse.icon(icon);
}
if (icon === null) {
return null;
}
if (_typeof(icon) === 'object' && icon.prefix && icon.iconName) {
return icon;
}
if (Array.isArray(icon) && icon.length === 2) {
return {
prefix: icon[0],
iconName: icon[1]
};
}
if (typeof icon === 'string') {
return {
prefix: 'fas',
iconName: icon
};
}
}
var FontAwesomeIcon = defineComponent({
name: 'FontAwesomeIcon',
props: {
border: {
type: Boolean,
default: false
},
fixedWidth: {
type: Boolean,
default: false
},
flip: {
type: [Boolean, String],
default: false,
validator: function validator(value) {
return [true, false, 'horizontal', 'vertical', 'both'].indexOf(value) > -1;
}
},
icon: {
type: [Object, Array, String],
required: true
},
mask: {
type: [Object, Array, String],
default: null
},
maskId: {
type: String,
default: null
},
listItem: {
type: Boolean,
default: false
},
pull: {
type: String,
default: null,
validator: function validator(value) {
return ['right', 'left'].indexOf(value) > -1;
}
},
pulse: {
type: Boolean,
default: false
},
rotation: {
type: [String, Number],
default: null,
validator: function validator(value) {
return [90, 180, 270].indexOf(Number.parseInt(value, 10)) > -1;
}
},
swapOpacity: {
type: Boolean,
default: false
},
size: {
type: String,
default: null,
validator: function validator(value) {
return ['2xs', 'xs', 'sm', 'lg', 'xl', '2xl', '1x', '2x', '3x', '4x', '5x', '6x', '7x', '8x', '9x', '10x'].indexOf(value) > -1;
}
},
spin: {
type: Boolean,
default: false
},
transform: {
type: [String, Object],
default: null
},
symbol: {
type: [Boolean, String],
default: false
},
title: {
type: String,
default: null
},
titleId: {
type: String,
default: null
},
inverse: {
type: Boolean,
default: false
},
bounce: {
type: Boolean,
default: false
},
shake: {
type: Boolean,
default: false
},
beat: {
type: Boolean,
default: false
},
fade: {
type: Boolean,
default: false
},
beatFade: {
type: Boolean,
default: false
},
flash: {
type: Boolean,
default: false
},
spinPulse: {
type: Boolean,
default: false
},
spinReverse: {
type: Boolean,
default: false
}
},
setup: function setup(props, _ref) {
var attrs = _ref.attrs;
var icon$1 = computed(function () {
return normalizeIconArgs(props.icon);
});
var classes = computed(function () {
return objectWithKey('classes', classList(props));
});
var transform = computed(function () {
return objectWithKey('transform', typeof props.transform === 'string' ? parse.transform(props.transform) : props.transform);
});
var mask = computed(function () {
return objectWithKey('mask', normalizeIconArgs(props.mask));
});
var renderedIcon = computed(function () {
return icon(icon$1.value, _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, classes.value), transform.value), mask.value), {}, {
symbol: props.symbol,
title: props.title,
titleId: props.titleId,
maskId: props.maskId
}));
});
watch(renderedIcon, function (value) {
if (!value) {
return log('Could not find one or more icon(s)', icon$1.value, mask.value);
}
}, {
immediate: true
});
var vnode = computed(function () {
return renderedIcon.value ? convert(renderedIcon.value.abstract[0], {}, attrs) : null;
});
return function () {
return vnode.value;
};
}
});
var FontAwesomeLayers = defineComponent({
name: 'FontAwesomeLayers',
props: {
fixedWidth: {
type: Boolean,
default: false
}
},
setup: function setup(props, _ref) {
var slots = _ref.slots;
var familyPrefix = config.familyPrefix;
var className = computed(function () {
return ["".concat(familyPrefix, "-layers")].concat(_toConsumableArray(props.fixedWidth ? ["".concat(familyPrefix, "-fw")] : []));
});
return function () {
return h('div', {
class: className.value
}, slots.default ? slots.default() : []);
};
}
});
var FontAwesomeLayersText = defineComponent({
name: 'FontAwesomeLayersText',
props: {
value: {
type: [String, Number],
default: ''
},
transform: {
type: [String, Object],
default: null
},
counter: {
type: Boolean,
default: false
},
position: {
type: String,
default: null,
validator: function validator(value) {
return ['bottom-left', 'bottom-right', 'top-left', 'top-right'].indexOf(value) > -1;
}
}
},
setup: function setup(props, _ref) {
var attrs = _ref.attrs;
var familyPrefix = config.familyPrefix;
var classes = computed(function () {
return objectWithKey('classes', [].concat(_toConsumableArray(props.counter ? ["".concat(familyPrefix, "-layers-counter")] : []), _toConsumableArray(props.position ? ["".concat(familyPrefix, "-layers-").concat(props.position)] : [])));
});
var transform = computed(function () {
return objectWithKey('transform', typeof props.transform === 'string' ? parse.transform(props.transform) : props.transform);
});
var abstractElement = computed(function () {
var _text = text(props.value.toString(), _objectSpread2(_objectSpread2({}, transform.value), classes.value)),
abstract = _text.abstract;
if (props.counter) {
abstract[0].attributes.class = abstract[0].attributes.class.replace('fa-layers-text', '');
}
return abstract[0];
});
var vnode = computed(function () {
return convert(abstractElement.value, {}, attrs);
});
return function () {
return vnode.value;
};
}
});
export { FontAwesomeIcon, FontAwesomeLayers, FontAwesomeLayersText };

File diff suppressed because one or more lines are too long

View file

@ -15,8 +15,8 @@ class UpgradeView(base.UpgradeView):
projects.update({
'rattail_demo': {
'commit_url': 'https://kallithea.rattailproject.org/rattail-project/rattail-demo/changelog/{new_version}/?size=10',
'release_url': 'https://kallithea.rattailproject.org/rattail-project/rattail-demo/files/{new_version}/CHANGES.rst',
'commit_url': 'https://forgejo.wuttaproject.org/rattail/rattail-demo/compare/{{old_version}}...{{new_version}}',
'release_url': 'https://forgejo.wuttaproject.org/rattail/rattail-demo/src/tag/v{{new_version}}/CHANGELOG.md',
},
})

View file

@ -3,50 +3,11 @@
Rattail Demo web API
"""
from __future__ import unicode_literals, absolute_import
from pyramid.config import Configurator
from pyramid.authentication import SessionAuthenticationPolicy
from tailbone import app
from tailbone.auth import TailboneAuthorizationPolicy
from tailbone import webapi as base
def main(global_config, **settings):
"""
This function returns a Pyramid WSGI application.
"""
# make config objects
rattail_config = app.make_rattail_config(settings)
pyramid_config = Configurator(settings=settings, root_factory=app.Root)
# configure user authorization / authentication
pyramid_config.set_authorization_policy(TailboneAuthorizationPolicy())
pyramid_config.set_authentication_policy(SessionAuthenticationPolicy())
# always require CSRF token protection
pyramid_config.set_default_csrf_options(require_csrf=True, token='_csrf', header='X-XSRF-TOKEN')
# bring in some Pyramid goodies
pyramid_config.include('tailbone.beaker')
pyramid_config.include('pyramid_tm')
pyramid_config.include('cornice')
# bring in the pyramid_retry logic, if available
# TODO: pretty soon we can require this package, hopefully..
try:
import pyramid_retry
except ImportError:
pass
else:
pyramid_config.include('pyramid_retry')
# add some permissions magic
pyramid_config.add_directive('add_tailbone_permission_group', 'tailbone.auth.add_permission_group')
pyramid_config.add_directive('add_tailbone_permission', 'tailbone.auth.add_permission')
# bring in some Tailbone
pyramid_config.include('tailbone.subscribers')
pyramid_config.include('tailbone.api')
return pyramid_config.make_wsgi_app()
return base.main(global_config, **settings)

23
tasks.py Normal file
View file

@ -0,0 +1,23 @@
# -*- coding: utf-8; -*-
"""
Tasks for rattail-demo
"""
import os
import shutil
from invoke import task
@task
def release(c):
"""
Release a new version of 'rattail-demo'
"""
if os.path.exists('dist'):
shutil.rmtree('dist')
if os.path.exists('rattail_demo.egg-info'):
shutil.rmtree('rattail_demo.egg-info')
c.run('python -m build --sdist')
c.run('twine upload dist/*')