Compare commits

...

136 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
Lance Edgar cc9ec47857 bump: version 0.1.0 → 0.2.0 2024-06-10 20:55:54 -05:00
Lance Edgar 5176acaf9e feat: switch from setup.cfg to pyproject.toml + hatchling 2024-06-10 20:54:22 -05:00
Lance Edgar c65cfe5144 Remove version cap for invoke 2024-06-09 22:29:47 -05:00
Lance Edgar 6b7c69265f Declare dependency for typer 2024-05-15 15:45:53 -05:00
Lance Edgar 8287f02793 Migrate all commands to use 'typer' framework 2024-05-15 15:34:42 -05:00
Lance Edgar d819d833af Add new wutta-style entry point group for subcommands 2023-11-21 14:18:17 -06:00
Lance Edgar ffe97e3a12 Fix class names for rattail-demo import-self per upstream changes 2023-09-25 13:38:16 -05:00
Lance Edgar ba534fbae1 Replace setup.py contents with setup.cfg 2023-05-16 13:19:21 -05:00
Lance Edgar 97db13a19f Tweak project/essentials view config per upstream changes 2023-05-05 10:46:04 -05:00
Lance Edgar 3fb08869e0 Avoid deprecated import for OrderedDict 2023-05-05 01:55:15 -05:00
Lance Edgar 83a810cb0d Peg invoke version to pre-2.1
apparently 2.1.1 has same problem as 2.1.0

hopefully can remove this in a few weeks; cf.
https://github.com/fabric/fabric/issues/2263#issuecomment-1532107759
2023-05-03 10:23:25 -05:00
Lance Edgar ecbc9abb10 Add version restriction for invoke lib
per bug in latest release
2023-04-30 21:05:32 -05:00
Lance Edgar 492cdc0329 Remove version cap for plaster lib
no longer relevant here
2023-03-08 20:48:23 -06:00
Lance Edgar 1c1611df88 Remove dependency for pyramid_retry
since tailbone now includes it
2023-02-11 22:25:49 -06:00
Lance Edgar e7248b4589 Include the 'essential' views 2023-01-18 18:42:55 -06:00
Lance Edgar 400133a98d Add support for new-style rattail-demo install command
also use upstream menus where possible
2023-01-18 14:59:26 -06:00
Lance Edgar 316bdfe1aa Use handler to build menus 2023-01-14 16:13:58 -06:00
Lance Edgar 6a8096391f Remove old Piwik analytics header code 2022-12-10 09:23:10 -06:00
Lance Edgar 34a3ee8f36 Refactor various views and config, per upstream changes
adding proper integration support for CORE, WooCommerce
2022-12-10 09:21:42 -06:00
Lance Edgar 5481c115ba Remove version cap for mysql-connector-python
live demo now runs on python 3.9.2 and so whatever issue i ran across
previously, is no longer a thing i guess.

might as well not declare any particular python version beyond "3" ...

also cleanup the rattail dependencies a bit
2022-11-26 20:32:45 -06:00
Lance Edgar 16fdfd51ba Fix view config inheritance 2022-11-21 22:22:46 -06:00
Lance Edgar 640ca783c7 Allow running web app via ASGI 2022-11-21 22:08:55 -06:00
Lance Edgar e6d4f7e303 Cap version of plaster lib, per recent errors 2022-11-20 16:27:56 -06:00
Lance Edgar 5cfe10c2e7 Rename menu entry for Luigi Tasks 2022-11-20 16:27:49 -06:00
Lance Edgar 9fc9138bce Expose views for datasync status, luigi jobs 2022-08-21 18:47:16 -05:00
Lance Edgar a6871fac95 Tweak menu config, handheld views per upstream changes 2022-08-21 11:35:03 -05:00
Lance Edgar a475db3fd5 Remove unwanted product label printing config 2022-01-31 19:37:33 -06:00
Lance Edgar 655dbea531 Tweak menu for generating new report, per upstream changes 2022-01-31 19:37:12 -06:00
Lance Edgar e23e65155f Expose views for reports, problem reports 2021-12-07 18:06:12 -06:00
Lance Edgar 1f37a4bd37 Update some view config
per upstream changes
2021-11-29 17:39:39 -06:00
Lance Edgar 0c73ee07d6 Add views, menu for Trainwreck 2021-02-18 20:20:31 -06:00
Lance Edgar 3384de9b63 Use custom Purchase views per CORE integration 2021-02-02 12:06:43 -06:00
Lance Edgar be2858084b Purge things for legacy (jquery) mobile app
per upstream purge
2021-01-30 15:54:40 -06:00
Lance Edgar 4288e1ce64 Override the CORE-API -> Rattail Demo importer for Store data
this lets the demo have random extra Store records and the importer will just
ignore them
2021-01-28 13:29:22 -06:00
Lance Edgar 8cb42712fd Include custom Stores view, per CORE 2021-01-27 22:43:56 -06:00
Lance Edgar 06f959cd09 Expose the Units of Measure views in web app
also this declares the CORE-specific product handler to be our default
2021-01-21 17:42:06 -06:00
Lance Edgar 2c8cc67043 Bugfix per woocommerce demo launch 2021-01-20 23:15:46 -06:00
Lance Edgar 25bf17715c Add initial support for WooCommerce integration 2021-01-20 21:54:52 -06:00
Lance Edgar 05ac07d2e6 Remove custom logic to protect chuck's person, employee records 2020-09-23 16:40:58 -05:00
Lance Edgar 2a34243e33 Remove custom logic for preventing password change for 'chuck'
upstream now handles this
2020-09-23 16:30:00 -05:00
Lance Edgar 1e06066353 Use default CORE-POS menu 2020-09-16 16:15:53 -05:00
Lance Edgar 2619869ceb Add menu links to generate new project, plus docs, source code etc. 2020-09-06 14:47:44 -05:00
Lance Edgar 34ab56247b Tweak changelog URL config for upgrade pkg diff table 2020-09-05 17:49:46 -05:00
Lance Edgar b9cf911688 Expose Product.corepos_id for editing 2020-08-20 20:24:15 -05:00
Lance Edgar 94a311828f Expose Product.item_id when editing 2020-08-20 19:57:07 -05:00
Lance Edgar 6e51686892 Add menu entry for CORE Super Departments 2020-08-20 18:05:25 -05:00
Lance Edgar 2d3c4fd935 Expose purchasing views 2020-08-20 17:56:55 -05:00
Lance Edgar 852f9d4902 Add initial Shopfoo pattern feature
data models, import/export, web views etc.
2020-08-19 22:21:00 -05:00
Lance Edgar a7656928f5 Cap version for mysql-connector
dang it, what is up with that thing?  oh well, can look into it later..
2020-08-19 19:49:32 -05:00
Lance Edgar f74101e7ae Refer to rattail tutorial for new project starting point 2020-08-12 21:53:12 -05:00
Lance Edgar 4218d466ae Tweak wording for readme 2020-08-12 21:36:53 -05:00
Lance Edgar ebe0c2b479 Delete unused fabfile 2020-08-06 02:11:12 -05:00
Lance Edgar c5922c74ea Be smarter about how we prevent edit/delete for some people, employees
instead of just hard-coding UUID for 'chuck'
2020-08-06 01:53:45 -05:00
Lance Edgar 25a7d46588 Stop protecting certain settings from being edited
we no longer expose settings to 'chuck' user etc.
2020-08-06 01:35:29 -05:00
Lance Edgar 310baea00f Tweak how we show pyCOREPOS package version
per upstream changes
2020-07-15 21:45:48 -05:00
Lance Edgar 0729a8902f Use some CORE-specific master views 2020-07-07 20:13:52 -05:00
Lance Edgar b01f8425e5 Fix inventory batch views
per upstream changes
2020-07-07 19:29:22 -05:00
Lance Edgar b044790d51 Add Tempmon Appliance views, menu link
also remove edit/delete restrictions for tempmon clients, probes.  now we just
let the normal permissions handle that
2020-03-22 16:39:42 -05:00
Lance Edgar 099fe14de1 Expose Members master view; add CORE link to member tab of profile view 2020-03-18 11:31:55 -05:00
Lance Edgar 89a4bc50d7 Add button to view Customer in CORE Office, from profile view 2020-03-17 12:29:48 -05:00
Lance Edgar 17f443b36a Use version importer from rattail-corepos by default 2020-03-16 20:44:41 -05:00
Lance Edgar 366594c53a Use custom Person views from tailbone-corepos 2020-03-16 19:47:17 -05:00
Lance Edgar f1a01af3c9 Remove custom logic for User views
this was to protect the 'chuck' user but we now have better (upstream) ways
2020-03-15 11:43:06 -05:00
Lance Edgar 69316183f7 Remove time clock, schedule views
those weren't very well supported, and that won't change anytime soon
2020-03-15 09:22:13 -05:00
Lance Edgar 92517605d8 Tweak project name for tailbone-corepos
i.e. for About page
2020-03-15 09:21:37 -05:00
Lance Edgar 0bf3fcfba4 Add support for CORE-POS jump button when viewing Department 2020-03-14 21:03:48 -05:00
Lance Edgar 07a36982d7 Move jump button logic to tailbone-corepos
since that now supports jump button for Vendor also, maybe others to come
2020-03-14 19:50:35 -05:00
Lance Edgar d7462f415d Add jump button, to view product in CORE Office 2020-03-14 18:33:08 -05:00
Lance Edgar 72a4c347d8 Define custom data model; add alembic scripts; etc.
actually our "custom" data model is just rattail + rattail-corepos
2020-03-04 19:08:20 -06:00
Lance Edgar f4a1868aaf Officially declare rattail-demo to be python3-only 2020-03-04 14:15:45 -06:00
Lance Edgar ece361a0e6 Fix basic product editing feature
limit which fields are supported
2020-02-28 18:12:32 -06:00
Lance Edgar db70870921 Fix home, login templates for Buefy
per recent upstream changes
2019-08-04 21:53:12 -05:00
Lance Edgar 5c3a95915b Remove CORE-POS web views logic
all the views are still visible in our app, but the view logic belongs in
tailbone-corepos now
2019-07-09 17:34:03 -05:00
Lance Edgar e4620c8670 Fix tempmon email config usage 2019-06-15 19:06:19 -05:00
Lance Edgar a0ba89ecfd Mock out the tempmon client restart action when testing 2019-06-15 17:31:29 -05:00
Lance Edgar d42f6a065b Add rattail dependency, for sake of eager upgrades
to bring in sqlalchemy-continuum  upgrades etc.
2019-06-15 15:32:10 -05:00
Lance Edgar f7eac8d707 Add support for Person Note views 2019-06-15 15:31:09 -05:00
Lance Edgar e401549e29 Remove deprecated menu template
using "simple" menus exclusively now
2019-04-02 21:51:00 -05:00
Lance Edgar 5ebe3a001a Update simple menus per newer conventions 2019-04-02 21:47:10 -05:00
Lance Edgar b0b7fc42b9 Add views for CORE-POS members 2019-03-06 18:24:00 -06:00
Lance Edgar b1875f5834 Add support for simple postgres restart 2019-03-03 13:10:08 -06:00
Lance Edgar 520940bc81 Delete 'bobcat' theme override
core bobcat theme now supports simple menus natively
2018-11-29 01:56:03 -06:00
Lance Edgar 599bf7cc32 Refactor web app to use "simple" menus
for sake of sharing between themes
2018-11-29 01:40:18 -06:00
Lance Edgar 15c44196bc Add menu for new 'bobcat' theme 2018-11-28 23:53:19 -06:00
Lance Edgar 71c60c58a1 Tweak style for header logo
seemed necessary for sake of bobcat theme, but worked for default too..
2018-11-28 21:41:00 -06:00
Lance Edgar 9294d95ffb Add proper "retry" support for basic postgres restart 2018-11-28 19:52:57 -06:00
Lance Edgar b0a52dcec2 Fix template references for app_title
make sure we only look to /base_meta.mako for that
2018-11-27 18:20:56 -06:00
Lance Edgar 46867b1154 Move base template to /base_meta.mako
per upstream 'themes' feature
2018-11-27 17:57:46 -06:00
Lance Edgar e3d9d7d4e5 Tweak some menu option labels 2018-11-26 22:08:41 -06:00
Lance Edgar 57c5f8ba5e Add support for basic web API app 2018-11-26 22:08:29 -06:00
Lance Edgar b7512e0b31 Add feature demo for importing Square transactions to CORE-POS
also allow delete of CORE-POS transactions, for sake of demo
2018-11-22 20:38:36 -06:00
Lance Edgar e20a9b8123 Add basic CORE-POS table views 2018-11-22 16:48:06 -06:00
Lance Edgar 452dc934fa Fix import bug for email settings 2018-08-17 13:11:52 -05:00
Lance Edgar e91858b567 Add (protected) email settings for upgrade success, failure 2018-08-17 13:08:48 -05:00
Lance Edgar 12c8b3b8dd Remove version caps for pyramid; stop auto-config for postgres error retry 2018-08-17 12:57:24 -05:00
Lance Edgar ba95d39780 Add version caps for pyramid etc.
so that we don't have to cap things within tailbone
2018-08-17 11:58:11 -05:00
Lance Edgar caaf2842dc Rearrange menu a bit, add rattail-corepos dependency 2018-06-03 00:44:10 -05:00
Lance Edgar 8ed906020b Replace rattail.fablib with rattail-fabric 2018-02-26 15:14:44 -06:00
Lance Edgar 42e2373b08 Add 'invoke' dependency 2018-02-13 19:48:45 -06:00
Lance Edgar d5a9b5c471 Change how we prevent edit/delete for demo users
to take new 'demo' user into account, but also more readable
2017-08-13 23:47:17 -05:00
Lance Edgar 82d5bcc0ed Add support for user events 2017-08-13 23:12:38 -05:00
Lance Edgar 8df12a1774 Add basic upgrades support, remove 'better' theme references 2017-08-13 22:37:38 -05:00
Lance Edgar fe19b5ae65 Replace occurrence of execfile() 2017-07-06 16:26:04 -05:00
Lance Edgar b9021afd96 Upgrade tempmon database when updating production demo 2017-04-01 16:31:52 -05:00
Lance Edgar ed1cc301a3 Add base mobile template, for custom menu 2017-03-30 21:46:06 -05:00
Lance Edgar 7947f89b5f Add Piwik tracking code 2017-01-11 21:11:03 -06:00
Lance Edgar 9b9bd8ea0f Fetch a web page after restarting apache when updating production demo 2016-12-16 21:23:35 -06:00
Lance Edgar bae33491b6 Refactor mobile views a bit, per tailbone changes 2016-12-12 16:03:42 -06:00
Lance Edgar e9998cb597 Add basic support for datasync views
Still no actual daemon wired up yet, but can fake it for UI with this
2016-12-11 21:21:44 -06:00
Lance Edgar db9deffaa6 Add '+dev' suffix to all versions on about page 2016-12-11 19:42:26 -06:00
Lance Edgar b5c81244ac Add initial support for mobile login / user menu etc. 2016-12-11 18:08:39 -06:00
Lance Edgar 9d9bc58a99 Add initial mobile template support
Also add rattail-tempmon to list of packages on 'about' page
2016-12-11 01:40:41 -06:00
Lance Edgar fcb21b9671 Add initial support for employee schedule views 2016-12-10 16:04:34 -06:00
Lance Edgar 7a22b11e2e Add support for messaging system 2016-12-10 15:40:21 -06:00
Lance Edgar d812e5465a Add overrides for email profiles, settings views
to make feedback email config readonly
2016-12-10 14:55:36 -06:00
Lance Edgar 365d5847a4 Add email config views 2016-12-10 14:16:37 -06:00
Lance Edgar c61f5c8053 Only allow 'demo' tempmon client to be restarted 2016-12-10 11:56:58 -06:00
Lance Edgar 50509f8f52 Add support for tempmon views 2016-12-10 11:24:01 -06:00
Lance Edgar 94993cf553 Add people.autocomplete view config 2016-12-10 10:15:41 -06:00
Lance Edgar 7f35fd828a Add fab tasks: release, update_production 2016-12-08 15:10:25 -06:00
53 changed files with 31127 additions and 383 deletions

3
.gitignore vendored
View file

@ -1 +1,4 @@
*~
*.pyc
dist/
rattail_demo.egg-info/

46
CHANGELOG.md Normal file
View file

@ -0,0 +1,46 @@
# Changelog
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
- switch from setup.cfg to pyproject.toml + hatchling
## v0.1.0 (2016-12-08)
- initial release

View file

@ -1,8 +0,0 @@
CHANGELOG
=========
0.1.0 (2016-12-08)
------------------
* Initial release

9
README.md Normal file
View file

@ -0,0 +1,9 @@
# Rattail Demo
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.

View file

@ -1,11 +0,0 @@
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.
Note that it also aims to be usable as a starting point for your own
project(s), should you need one.
.. _`Rattail Wiki`: https://rattailproject.org/moin/

61
pyproject.toml Normal file
View file

@ -0,0 +1,61 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "rattail-demo"
version = "0.2.4"
description = "Rattail Software Demo"
readme = "README.md"
authors = [{name = "Lance Edgar", email = "lance@edbob.org"}]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Topic :: Office/Business",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"invoke",
"psycopg2",
"rattail-tempmon",
"Tailbone",
"tailbone-corepos",
"tailbone-woocommerce",
"typer",
"xlrd",
]
[project.scripts]
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"
[project.entry-points."rattail.config.extensions"]
rattail-demo = "rattail_demo.config:DemoConfigExtension"
[project.urls]
Homepage = "https://demo.rattailproject.org"
Repository = "https://forgejo.wuttaproject.org/rattail/rattail-demo"
Changelog = "https://forgejo.wuttaproject.org/rattail/rattail-demo/src/branch/master/CHANGELOG.md"
[tool.commitizen]
version_provider = "pep621"
tag_format = "v$version"
update_changelog_on_bump = true

View file

@ -1,3 +1,6 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8; -*-
__version__ = u'0.1.0'
from importlib.metadata import version
__version__ = version('rattail-demo')

140
rattail_demo/commands.py Normal file
View file

@ -0,0 +1,140 @@
# -*- coding: utf-8; -*-
"""
Rattail Demo Commands
"""
import datetime
import logging
import os
import shutil
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
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)"
)
@rattail_demo_typer.command()
@file_exporter_command
def export_shopfoo(
ctx: typer.Context,
**kwargs
):
"""
Export data to the Harvest system
"""
config = ctx.parent.rattail_config
progress = ctx.parent.rattail_progress
handler = ImportCommandHandler(
config, import_handler_spec='rattail_demo.shopfoo.importing.rattail:FromRattailToShopfoo')
kwargs['user'] = typer_get_runas_user(ctx)
kwargs['handler_kwargs'] = {'output_dir': kwargs['output_dir']}
handler.run(kwargs, progress=progress)
@rattail_demo_typer.command()
@importer_command
def import_self(
ctx: typer.Context,
**kwargs
):
"""
Update "cascading" Rattail data based on "core" Rattail data
"""
config = ctx.parent.rattail_config
progress = ctx.parent.rattail_progress
handler = ImportCommandHandler(
config, import_handler_spec='rattail_demo.importing.local:FromRattailDemoToSelf')
kwargs['user'] = typer_get_runas_user(ctx)
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,
):
"""
Install the Rattail Demo app
"""
from rattail.install import InstallHandler
config = ctx.parent.rattail_config
handler = InstallHandler(config,
app_title="Rattail Demo",
app_package='rattail_demo',
app_eggname='rattail_demo',
app_pypiname='rattail_demo')
handler.run()

32
rattail_demo/config.py Normal file
View file

@ -0,0 +1,32 @@
# -*- coding: utf-8; -*-
"""
Rattail Demo config extension
"""
from wuttjamaican.conf import WuttaConfigExtension
class DemoConfigExtension(WuttaConfigExtension):
"""
Rattail Demo config extension
"""
key = 'rattail-demo'
def configure(self, config):
config.setdefault('rattail', 'app_package', 'rattail_demo')
# tell rattail where our stuff lives
config.setdefault('rattail', 'model_spec', 'rattail_demo.db.model')
config.setdefault('rattail.trainwreck', 'model', 'rattail.trainwreck.db.model.defaults')
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')
# default import handlers
config.setdefault('rattail.importing', 'versions.handler', 'rattail_demo.importing.versions:FromRattailDemoToRattailDemoVersions')
config.setdefault('rattail.importing', 'corepos_api.handler', 'rattail_demo.importing.corepos_api:FromCOREPOSToRattail')

View file

View file

@ -0,0 +1,28 @@
# -*- coding: utf-8; mode: python; -*-
# -*- coding: utf-8 -*-
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
from alembic import op
import sqlalchemy as sa
import rattail.db.types
${imports if imports else ""}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View file

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
"""initial tables
Revision ID: 2108f9efa758
Revises: efb7cd318947
Create Date: 2020-08-19 20:02:15.501843
"""
# revision identifiers, used by Alembic.
revision = '2108f9efa758'
down_revision = None
branch_labels = ('rattail_demo',)
depends_on = None
from alembic import op
import sqlalchemy as sa
import rattail.db.types
def upgrade():
# demo_shopfoo_product
op.create_table('demo_shopfoo_product',
sa.Column('uuid', sa.String(length=32), nullable=False),
sa.Column('product_uuid', sa.String(length=32), nullable=True),
sa.Column('upc', sa.String(length=14), nullable=True),
sa.Column('description', sa.String(length=255), nullable=True),
sa.Column('price', sa.Numeric(precision=13, scale=2), nullable=True),
sa.Column('enabled', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['product_uuid'], ['product.uuid'], name='demo_shopfoo_product_fk_product'),
sa.PrimaryKeyConstraint('uuid')
)
op.create_table('demo_shopfoo_product_version',
sa.Column('uuid', sa.String(length=32), autoincrement=False, nullable=False),
sa.Column('product_uuid', sa.String(length=32), autoincrement=False, nullable=True),
sa.Column('upc', sa.String(length=14), autoincrement=False, nullable=True),
sa.Column('description', sa.String(length=255), autoincrement=False, nullable=True),
sa.Column('price', sa.Numeric(precision=13, scale=2), autoincrement=False, nullable=True),
sa.Column('enabled', sa.Boolean(), autoincrement=False, nullable=True),
sa.Column('transaction_id', sa.BigInteger(), autoincrement=False, nullable=False),
sa.Column('end_transaction_id', sa.BigInteger(), nullable=True),
sa.Column('operation_type', sa.SmallInteger(), nullable=False),
sa.PrimaryKeyConstraint('uuid', 'transaction_id')
)
op.create_index(op.f('ix_demo_shopfoo_product_version_end_transaction_id'), 'demo_shopfoo_product_version', ['end_transaction_id'], unique=False)
op.create_index(op.f('ix_demo_shopfoo_product_version_operation_type'), 'demo_shopfoo_product_version', ['operation_type'], unique=False)
op.create_index(op.f('ix_demo_shopfoo_product_version_transaction_id'), 'demo_shopfoo_product_version', ['transaction_id'], unique=False)
# demo_shopfoo_product_export
op.create_table('demo_shopfoo_product_export',
sa.Column('uuid', sa.String(length=32), nullable=False),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created', sa.DateTime(), nullable=False),
sa.Column('created_by_uuid', sa.String(length=32), nullable=False),
sa.Column('record_count', sa.Integer(), nullable=True),
sa.Column('filename', sa.String(length=255), nullable=True),
sa.Column('uploaded', sa.Boolean(), nullable=False),
sa.ForeignKeyConstraint(['created_by_uuid'], ['user.uuid'], name='demo_shopfoo_product_export_fk_created_by'),
sa.PrimaryKeyConstraint('uuid')
)
def downgrade():
# demo_shopfoo_product_export
op.drop_table('demo_shopfoo_product_export')
# demo_shopfoo_product
op.drop_index(op.f('ix_demo_shopfoo_product_version_transaction_id'), table_name='demo_shopfoo_product_version')
op.drop_index(op.f('ix_demo_shopfoo_product_version_operation_type'), table_name='demo_shopfoo_product_version')
op.drop_index(op.f('ix_demo_shopfoo_product_version_end_transaction_id'), table_name='demo_shopfoo_product_version')
op.drop_table('demo_shopfoo_product_version')
op.drop_table('demo_shopfoo_product')

View file

@ -0,0 +1,16 @@
# -*- coding: utf-8; -*-
"""
Rattail Demo data model
"""
# bring in all the normal stuff from Rattail
from rattail.db.model import *
# also bring in CORE-POS integration models
from rattail_corepos.db.model import *
# also bring in WooCommerce integration models
from rattail_woocommerce.db.model import *
# now bring in Demo-specific models
from .shopfoo import ShopfooProduct, ShopfooProductExport

View file

@ -0,0 +1,37 @@
# -*- coding: utf-8; -*-
"""
Database schema extensions for Shopfoo integration
"""
import sqlalchemy as sa
from rattail.db import model
from rattail.db.model.shopfoo import ShopfooProductBase, ShopfooProductExportBase
class ShopfooProduct(ShopfooProductBase, model.Base):
"""
Shopfoo-specific product cache table. Each record in this table *should*
match exactly, what is in the actual "Shopfoo" system (even though that's
made-up in this case).
"""
__tablename__ = 'demo_shopfoo_product'
__versioned__ = {}
upc = sa.Column(sa.String(length=14), nullable=True)
description = sa.Column(sa.String(length=255), nullable=True)
price = sa.Column(sa.Numeric(precision=13, scale=2), nullable=True)
enabled = sa.Column(sa.Boolean(), nullable=True)
def __str__(self):
return self.description or self.upc or ""
class ShopfooProductExport(ShopfooProductExportBase, model.Base):
"""
Shopfoo product exports
"""
__tablename__ = 'demo_shopfoo_product_export'

View file

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
"""
Importing into Rattail Demo
"""
from . import model

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8; -*-
"""
CORE-POS API -> Rattail Demo importing
"""
from rattail_corepos.importing.corepos import api as base
class FromCOREPOSToRattail(base.FromCOREPOSToRattail):
"""
Override some parts of CORE-POS API -> Rattail importing.
"""
def get_importers(self):
importers = super(FromCOREPOSToRattail, self).get_importers()
importers['Store'] = StoreImporter
return importers
class StoreImporter(base.StoreImporter):
"""
Tweak how we import Store data from CORE-POS API.
"""
def cache_query(self):
model = self.model
# we ignore any Store records which are not associated with CORE, so
# the importer will never be tempted to delete them etc.
return self.session.query(model.Store)\
.join(model.CoreStore)\
.filter(model.CoreStore.corepos_id != None)

View file

@ -0,0 +1,61 @@
# -*- coding: utf-8; -*-
"""
Rattail Demo -> Rattail Demo "self" data import
"""
from collections import OrderedDict
from rattail.importing.local import FromRattailSelfToRattail, FromRattailSelf
from rattail.importing.shopfoo import ShopfooProductImporterMixin
from rattail_demo import importing as rattail_demo_importing
class FromRattailDemoToSelf(FromRattailSelfToRattail):
"""
Handler for Rattail Demo -> Rattail Demo ("self") imports
"""
def get_importers(self):
importers = OrderedDict()
importers['ShopfooProduct'] = ShopfooProductImporter
return importers
class ShopfooProductImporter(ShopfooProductImporterMixin, FromRattailSelf, rattail_demo_importing.model.ShopfooProductImporter):
"""
Product -> ShopfooProduct
"""
supported_fields = [
'uuid',
'product_uuid',
'upc',
'description',
'price',
'enabled',
]
def normalize_base_product_data(self, product):
price = None
if product.regular_price:
price = product.regular_price.price
return {
'product_uuid': product.uuid,
'upc': str(product.upc or '') or None,
'description': product.full_description,
'price': price,
'enabled': True, # will maybe unset this in mark_unwanted()
}
def product_is_unwanted(self, product, data):
if super(ShopfooProductImporter, self).product_is_unwanted(product, data):
return True
if not data['price']: # let's say this is a required field for Shopfoo
return True
return False
def mark_unwanted(self, product, data):
data = super(ShopfooProductImporter, self).mark_unwanted(product, data)
data['enabled'] = False
return data

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8; -*-
"""
Rattail Demo model importers
"""
from rattail.importing.model import ToRattail
from rattail_demo.db import model
##############################
# custom models
##############################
class ShopfooProductImporter(ToRattail):
"""
Importer for ShopfooProduct data
"""
model_class = model.ShopfooProduct

View file

@ -0,0 +1,28 @@
# -*- coding: utf-8; -*-
"""
Rattail Demo -> Rattail Demo "versions" data import
"""
from rattail_demo.db import model
from rattail.importing import versions as base
from rattail_corepos.importing.versions import CoreposVersionMixin
from rattail_woocommerce.importing.versions import WooVersionMixin
class FromRattailDemoToRattailDemoVersions(base.FromRattailToRattailVersions,
CoreposVersionMixin,
WooVersionMixin):
"""
Handler for Rattail Demo -> Rattail Demo "versions" data import
"""
def get_importers(self):
importers = super(FromRattailDemoToRattailDemoVersions, self).get_importers()
importers = self.add_corepos_importers(importers)
importers = self.add_woocommerce_importers(importers)
importers['ShopfooProduct'] = ShopfooProductImporter
return importers
class ShopfooProductImporter(base.VersionImporter):
host_model_class = model.ShopfooProduct

View file

View file

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
"""
Importing into Shopfoo
"""
from . import model

View file

@ -0,0 +1,28 @@
# -*- coding: utf-8; -*-
"""
Shopfoo model importers
"""
from rattail_demo.db import model
from rattail.importing.exporters import ToCSV
from rattail.shopfoo.importing.model import ProductImporterMixin
class ToShopfoo(ToCSV):
pass
class ProductImporter(ProductImporterMixin, ToShopfoo):
"""
Shopfoo product data importer
"""
key = 'uuid'
simple_fields = [
'uuid',
'product_uuid',
'upc',
'description',
'price',
'enabled',
]
export_model_class = model.ShopfooProductExport

View file

@ -0,0 +1,63 @@
# -*- coding: utf-8; -*-
"""
Rattail -> Shopfoo importing
"""
from collections import OrderedDict
from rattail import importing
from rattail_demo.db import model
from rattail_demo.shopfoo import importing as shopfoo_importing
from rattail.shopfoo.importing.rattail import ProductImporterMixin
class FromRattailToShopfoo(importing.FromRattailHandler):
"""
Rattail -> Shopfoo import handler
"""
host_title = "Rattail"
local_title = "Shopfoo"
direction = 'export'
def get_importers(self):
importers = OrderedDict()
importers['Product'] = ProductImporter
return importers
class FromRattail(importing.FromSQLAlchemy):
"""
Base class for Shopfoo -> Rattail importers
"""
class ProductImporter(ProductImporterMixin, FromRattail, shopfoo_importing.model.ProductImporter):
"""
Product data importer
"""
host_model_class = model.ShopfooProduct
supported_fields = [
'uuid',
'product_uuid',
'upc',
'description',
'price',
'enabled',
]
def query(self):
return self.host_session.query(model.ShopfooProduct)\
.order_by(model.ShopfooProduct.upc)
def normalize_host_object(self, product):
# copy all values "as-is" from our cache record
data = dict([(field, getattr(product, field))
for field in self.fields])
# TODO: is it ever a good idea to set this flag? doing so will mean
# the record is *not* included in CSV output file
# data['_deleted_'] = product.deleted_from_shopfoo
return data

View file

@ -1,35 +1,49 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8; -*-
"""
Pyramid web application
"""
from __future__ import unicode_literals, absolute_import
from tailbone import app
from tailbone_corepos.db import CoreOfficeSession, CoreTransSession
def main(global_config, **settings):
"""
This function returns a Pyramid WSGI application.
"""
# set some defaults for PostgreSQL
app.provide_postgresql_settings(settings)
# prefer demo templates over tailbone; use 'better' theme
# prefer demo templates over tailbone
settings.setdefault('mako.directories', ['rattail_demo.web:templates',
'tailbone:templates/themes/better',
'tailbone_corepos:templates',
'tailbone_woocommerce:templates',
'tailbone:templates',])
# for graceful handling of postgres restart
settings.setdefault('retry.attempts', 2)
# make config objects
rattail_config = app.make_rattail_config(settings)
pyramid_config = app.make_pyramid_config(settings)
# bring in rest of rattail-demo etc.
pyramid_config.include('tailbone.static')
pyramid_config.include('tailbone.subscribers')
# configure database sessions
CoreOfficeSession.configure(bind=rattail_config.corepos_engine)
CoreTransSession.configure(bind=rattail_config.coretrans_engine)
# 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')
# configure PostgreSQL some more
app.configure_postgresql(pyramid_config)
# for graceful handling of postgres restart
pyramid_config.add_tween('tailbone.tweens.sqlerror_tween_factory',
under='pyramid_tm.tm_tween_factory')
return pyramid_config.make_wsgi_app()
def asgi_main():
"""
This function returns an ASGI application.
"""
from tailbone.asgi import make_asgi_app
return make_asgi_app(main)

97
rattail_demo/web/menus.py Normal file
View file

@ -0,0 +1,97 @@
# -*- coding: utf-8; -*-
"""
Web Menus
"""
from tailbone import menus as base
from tailbone_corepos.menus import make_corepos_menu
class DemoMenuHandler(base.TailboneMenuHandler):
"""
Demo menu handler
"""
def make_menus(self, request, **kwargs):
people_menu = self.make_people_menu(request)
products_menu = self.make_products_menu(request)
vendors_menu = self.make_vendors_menu(request)
corepos_menu = make_corepos_menu(request)
shopfoo_menu = {
'title': "Shopfoo",
'type': 'menu',
'items': [
{
'title': "Products",
'route': 'shopfoo.products',
'perm': 'shopfoo.products.list',
},
{
'title': "Product Exports",
'route': 'shopfoo.product_exports',
'perm': 'shopfoo.product_exports.list',
},
{'type': 'sep'},
{
'title': "WooCommerce Products",
'route': 'woocommerce.products',
'perm': 'woocommerce.products.list',
},
],
}
reports_menu = self.make_reports_menu(request, include_trainwreck=True)
batch_menu = self.make_batches_menu(request)
tempmon_menu = self.make_tempmon_menu(request)
other_menu = {
'title': "Other",
'type': 'menu',
'items': [
{
'title': "Documentation",
'url': 'https://rattailproject.org/moin/RattailDemo',
'target': '_blank',
},
{
'title': "Source Code",
'url': 'https://forgejo.wuttaproject.org/rattail/rattail-demo',
'target': '_blank',
},
{
'title': "RattailProject.org",
'url': 'https://rattailproject.org',
'target': '_blank',
},
{'type': 'sep'},
{
'title': "Generate New Project",
'route': 'generated_projects.create',
'perm': 'generated_projects.create',
},
],
}
admin_menu = self.make_admin_menu(request, include_stores=True)
menus = [
people_menu,
products_menu,
vendors_menu,
corepos_menu,
shopfoo_menu,
reports_menu,
batch_menu,
tempmon_menu,
other_menu,
admin_menu,
]
return menus

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

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
"""
Pyramid Event Subscribers
"""
from __future__ import unicode_literals, absolute_import
def includeme(config):
config.include('tailbone.subscribers')
config.add_subscriber('tailbone.subscribers.add_inbox_count', 'pyramid.events.BeforeRender')

View file

@ -1,14 +0,0 @@
## -*- coding: utf-8 -*-
<%inherit file="tailbone:templates/themes/better/base.mako" />
<%def name="global_title()">${"[STAGE] " if not request.rattail_config.production() else ''}Rattail Demo</%def>
<%def name="favicon()">
<link rel="icon" type="image/x-icon" href="${request.static_url('tailbone:static/img/rattail.ico')}" />
</%def>
<%def name="header_logo()">
${h.image(request.static_url('tailbone:static/img/rattail.ico'), "Header Logo", height='49')}
</%def>
${parent.body()}

View file

@ -0,0 +1,6 @@
## -*- coding: utf-8; -*-
<%inherit file="tailbone:templates/base_meta.mako" />
<%def name="header_logo()">
${h.image(request.static_url('tailbone:static/img/rattail.ico'), "Header Logo", style="height: 55px;")}
</%def>

View file

@ -0,0 +1,11 @@
## -*- coding: utf-8; -*-
<%inherit file="/master/index.mako" />
<%def name="context_menu_items()">
${parent.context_menu_items()}
% if request.has_perm('{}.import_file'.format(permission_prefix)):
<li>${h.link_to("Import {} from Square CSV".format(model_title_plural), url('{}.import_square'.format(route_prefix)))}</li>
% endif
</%def>
${parent.body()}

View file

@ -1,7 +0,0 @@
## -*- coding: utf-8 -*-
<%inherit file="tailbone:templates/home.mako" />
<div class="logo">
${h.image(request.static_url('tailbone:static/img/home_logo.png'), "Rattail Logo")}
<h1>Welcome to the Rattail Demo</h1>
</div>

View file

@ -1,4 +1,4 @@
## -*- coding: utf-8 -*-
## -*- coding: utf-8; -*-
<%inherit file="tailbone:templates/login.mako" />
<%def name="extra_styles()">
@ -11,8 +11,12 @@
</style>
</%def>
${parent.body()}
<%def name="page_content()">
${parent.page_content()}
<p class="tips">
Login with <strong>chuck / admin</strong> for full demo access
</p>
</%def>
<p class="tips">
Login with <strong>chuck / admin</strong> for full demo access
</p>
${parent.body()}

View file

@ -1,110 +0,0 @@
## -*- coding: utf-8 -*-
<%def name="main_menu_items()">
% if request.has_any_perm('products.list', 'vendors.list', 'brands.list', 'families.list', 'reportcodes.list'):
<li>
<a>Products</a>
<ul>
% if request.has_perm('products.list'):
<li>${h.link_to("Products", url('products'))}</li>
% endif
% if request.has_perm('vendors.list'):
<li>${h.link_to("Vendors", url('vendors'))}</li>
% endif
% if request.has_perm('brands.list'):
<li>${h.link_to("Brands", url('brands'))}</li>
% endif
% if request.has_perm('families.list'):
<li>${h.link_to("Families", url('families'))}</li>
% endif
% if request.has_perm('reportcodes.list'):
<li>${h.link_to("Report Codes", url('reportcodes'))}</li>
% endif
</ul>
</li>
% endif
% if request.has_any_perm('people.list', 'customers.list', 'employees.list'):
<li>
<a>People</a>
<ul>
% if request.has_perm('people.list'):
<li>${h.link_to("All People", url('people'))}</li>
% endif
% if request.has_perm('customers.list'):
<li>${h.link_to("Customers", url('customers'))}</li>
% endif
% if request.has_perm('employees.list'):
<li>${h.link_to("Employees", url('employees'))}</li>
% endif
</ul>
</li>
% endif
% if request.has_any_perm('stores.list', 'departments.list', 'subdepartments.list'):
<li>
<a>Company</a>
<ul>
% if request.has_perm('stores.list'):
<li>${h.link_to("Stores", url('stores'))}</li>
% endif
% if request.has_perm('departments.list'):
<li>${h.link_to("Departments", url('departments'))}</li>
% endif
% if request.has_perm('subdepartments.list'):
<li>${h.link_to("Subdepartments", url('subdepartments'))}</li>
% endif
</ul>
</li>
% endif
% if request.has_any_perm('batch.handheld.list', 'batch.inventory.list'):
<li>
<a>Batches</a>
<ul>
% if request.has_perm('batch.handheld.list'):
<li>${h.link_to("Handheld", url('batch.handheld'))}</li>
% endif
% if request.has_perm('batch.inventory.list'):
<li>${h.link_to("Inventory", url('batch.inventory'))}</li>
% endif
</ul>
</li>
% endif
% if request.has_any_perm('users.list', 'roles.list', 'settings.list'):
<li>
<a>Admin</a>
<ul>
% if request.has_perm('users.list'):
<li>${h.link_to("Users", url('users'))}</li>
% endif
% if request.has_perm('roles.list'):
<li>${h.link_to("Roles", url('roles'))}</li>
% endif
% if request.has_perm('settings.list'):
<li>${h.link_to("Settings", url('settings'))}</li>
% endif
</ul>
</li>
% endif
% if request.user:
<li>
<a${' class="root-user"' if request.is_root else ''|n}>${request.user}${" ({})".format(inbox_count) if inbox_count else ''}</a>
<ul>
% if request.is_root:
<li class="root-user">${h.link_to("Stop being root", url('stop_root'))}</li>
% elif request.is_admin:
<li class="root-user">${h.link_to("Become root", url('become_root'))}</li>
% endif
<li>${h.link_to("Change Password", url('change_password'))}</li>
<li>${h.link_to("Logout", url('logout'))}</li>
</ul>
</li>
% else:
<li>${h.link_to("Login", url('login'))}</li>
% endif
</%def>

View file

@ -1,50 +1,57 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8; -*-
"""
Web views
"""
from __future__ import unicode_literals, absolute_import
from tailbone import views as base
def bogus_error(request):
"""
A special view which simply raises an error, for the sake of testing
uncaught exception handling.
"""
raise Exception("Congratulations, you have triggered a bogus error.")
from tailbone.views import essentials
def includeme(config):
# TODO: merge these views into core/common
config.add_route('home', '/')
config.add_view(base.home, route_name='home', renderer='/home.mako')
config.add_route('bogus_error', '/bogus-error')
config.add_view(bogus_error, route_name='bogus_error',
permission='admin')
# core views
config.include('rattail_demo.web.views.common')
config.include('rattail_demo.web.views.auth')
# tailbone essentials
essentials.defaults(config, **{
'tailbone.views.upgrades': 'rattail_demo.web.views.upgrades',
})
# main table views
config.include('tailbone.views.brands')
config.include('tailbone.views.categories')
config.include('tailbone.views.customers')
config.include('tailbone.views.customergroups')
config.include('tailbone.views.departments')
config.include('tailbone.views.employees')
config.include('tailbone.views.families')
config.include('rattail_demo.web.views.people')
config.include('tailbone.views.products')
config.include('tailbone.views.members')
config.include('tailbone.views.messages')
config.include('rattail_demo.web.views.products')
config.include('tailbone.views.reportcodes')
config.include('tailbone.views.roles')
config.include('tailbone.views.settings')
config.include('tailbone.views.stores')
config.include('tailbone.views.subdepartments')
config.include('rattail_demo.web.views.users')
config.include('tailbone.views.tempmon')
config.include('tailbone.views.vendors')
config.include('tailbone.views.uoms')
# purchasing / receiving
config.include('tailbone_corepos.views.purchases')
config.include('tailbone.views.purchases.credits')
config.include('tailbone.views.purchasing')
# core-pos views
config.include('tailbone_corepos.views')
config.include('tailbone_corepos.views.corepos')
# shopfoo views
config.include('rattail_demo.web.views.shopfoo')
# woocommerce views
config.include('tailbone_woocommerce.views')
config.include('tailbone_woocommerce.views.woocommerce')
# batch views
config.include('tailbone.views.handheld')
config.include('tailbone.views.inventory')
config.include('tailbone.views.batch.handheld')
config.include('tailbone.views.batch.inventory')
config.include('tailbone.views.batch.importer')
config.include('tailbone.views.batch.vendorcatalog')
# trainwreck
config.include('tailbone.views.trainwreck.defaults')

View file

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
"""
Auth views
"""
from __future__ import unicode_literals, absolute_import
from pyramid import httpexceptions
from tailbone.views import auth as base
def change_password(request):
# prevent password change for 'chuck'
if request.user and request.user.username == 'chuck':
request.session.flash("Cannot change password for 'chuck' in Rattail Demo")
return httpexceptions.HTTPFound(location=request.get_referrer())
return base.change_password(request)
def includeme(config):
# TODO: this is way too much duplication, surely..
base.add_routes(config)
config.add_forbidden_view(base.forbidden)
config.add_view(base.login, route_name='login',
renderer='/login.mako')
config.add_view(base.logout, route_name='logout')
config.add_view(base.become_root, route_name='become_root')
config.add_view(base.stop_root, route_name='stop_root')
config.add_view(change_password, route_name='change_password',
renderer='/change_password.mako')

View file

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
"""
Common views
"""
from __future__ import unicode_literals, absolute_import
from tailbone.views import common as base
import rattail_demo
class CommonView(base.CommonView):
project_title = "Rattail Demo"
project_version = rattail_demo.__version__
def includeme(config):
CommonView.defaults(config)

View file

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
"""
Person views
"""
from __future__ import unicode_literals, absolute_import
from tailbone.views import people as base
class PeopleView(base.PeopleView):
"""
Prevent edit/delete for Chuck Norris
"""
def editable_instance(self, person):
return person.uuid != '30d1fe06bcf411e6a7c23ca9f40bc550'
def deletable_instance(self, person):
return person.uuid != '30d1fe06bcf411e6a7c23ca9f40bc550'
def includeme(config):
PeopleView.defaults(config)

View file

@ -0,0 +1,28 @@
# -*- coding: utf-8; -*-
"""
Product views
"""
from webhelpers2.html import tags
from tailbone.views import products as base
class ProductView(base.ProductView):
"""
Product overrides for online demo
"""
def get_xref_links(self, product):
links = super(ProductView, self).get_xref_links(product)
if product.demo_shopfoo_product:
url = self.request.route_url('shopfoo.products.view',
uuid=product.demo_shopfoo_product.uuid)
links.append(tags.link_to("View Shopfoo Product", url))
return links
def includeme(config):
base.defaults(config, **{'ProductView': ProductView})

View file

@ -0,0 +1,9 @@
# -*- coding: utf-8; -*-
"""
Shopfoo views
"""
def includeme(config):
config.include('rattail_demo.web.views.shopfoo.products')
config.include('rattail_demo.web.views.shopfoo.exports')

View file

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
"""
Views for Shopfoo product exports
"""
from rattail_demo.db import model
from tailbone.views.exports import ExportMasterView
class ShopfooProductExportView(ExportMasterView):
"""
Master view for Shopfoo product exports.
"""
model_class = model.ShopfooProductExport
route_prefix = 'shopfoo.product_exports'
url_prefix = '/shopfoo/exports/product'
downloadable = True
editable = True
delete_export_files = True
grid_columns = [
'id',
'created',
'created_by',
'filename',
'record_count',
'uploaded',
]
form_fields = [
'id',
'created',
'created_by',
'record_count',
'filename',
'uploaded',
]
def includeme(config):
ShopfooProductExportView.defaults(config)

View file

@ -0,0 +1,70 @@
# -*- coding: utf-8; -*-
"""
Shopfoo product views
"""
from rattail_demo.db import model
from tailbone.views import MasterView
class ShopfooProductView(MasterView):
"""
Shopfoo Product views
"""
model_class = model.ShopfooProduct
url_prefix = '/shopfoo/products'
route_prefix = 'shopfoo.products'
creatable = False
editable = False
bulk_deletable = True
has_versions = True
labels = {
'upc': "UPC",
}
grid_columns = [
'upc',
'description',
'price',
'enabled',
]
form_fields = [
'product',
'upc',
'description',
'price',
'enabled',
]
def configure_grid(self, g):
super(ShopfooProductView, self).configure_grid(g)
g.filters['upc'].default_active = True
g.filters['upc'].default_verb = 'equal'
g.filters['description'].default_active = True
g.filters['description'].default_verb = 'contains'
g.set_sort_defaults('upc')
g.set_type('price', 'currency')
g.set_link('upc')
g.set_link('description')
def grid_extra_class(self, product, i):
if not product.enabled:
return 'warning'
def configure_form(self, f):
super(ShopfooProductView, self).configure_form(f)
f.set_renderer('product', self.render_product)
f.set_type('price', 'currency')
def includeme(config):
ShopfooProductView.defaults(config)

View file

@ -0,0 +1,27 @@
# -*- coding: utf-8; -*-
"""
Upgrade views
"""
import re
from tailbone.views import upgrades as base
class UpgradeView(base.UpgradeView):
def get_changelog_projects(self):
projects = super(UpgradeView, self).get_changelog_projects()
projects.update({
'rattail_demo': {
'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',
},
})
return projects
def includeme(config):
base.defaults(config, **{'UpgradeView': UpgradeView})

View file

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
"""
User views
"""
from __future__ import unicode_literals, absolute_import
from tailbone.views import users as base
class UsersView(base.UsersView):
"""
Prevent edit/delete for 'chuck'
"""
def editable_instance(self, user):
return user.uuid != '28eeee92bcf411e6a7c23ca9f40bc550'
def deletable_instance(self, user):
return user.uuid != '28eeee92bcf411e6a7c23ca9f40bc550'
def includeme(config):
UsersView.defaults(config)

View file

@ -0,0 +1,13 @@
# -*- coding: utf-8; -*-
"""
Rattail Demo web API
"""
from tailbone import webapi as base
def main(global_config, **settings):
"""
This function returns a Pyramid WSGI application.
"""
return base.main(global_config, **settings)

View file

@ -1,80 +0,0 @@
# -*- coding: utf-8 -*-
"""
Setup script for Rattail Demo
"""
from __future__ import unicode_literals, absolute_import
import os
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
execfile(os.path.join(here, 'rattail_demo', '_version.py'))
README = open(os.path.join(here, 'README.rst')).read()
requires = [
#
# Version numbers within comments below have specific meanings.
# Basically the 'low' value is a "soft low," and 'high' a "soft high."
# In other words:
#
# If either a 'low' or 'high' value exists, the primary point to be
# made about the value is that it represents the most current (stable)
# version available for the package (assuming typical public access
# methods) whenever this project was started and/or documented.
# Therefore:
#
# If a 'low' version is present, you should know that attempts to use
# versions of the package significantly older than the 'low' version
# may not yield happy results. (A "hard" high limit may or may not be
# indicated by a true version requirement.)
#
# Similarly, if a 'high' version is present, and especially if this
# project has laid dormant for a while, you may need to refactor a bit
# when attempting to support a more recent version of the package. (A
# "hard" low limit should be indicated by a true version requirement
# when a 'high' version is present.)
#
# In any case, developers and other users are encouraged to play
# outside the lines with regard to these soft limits. If bugs are
# encountered then they should be filed as such.
#
# package # low high
'psycopg2', # 2.6.2
'Tailbone', # 0.5.49
'xlrd', # 1.0.0
]
setup(
name = "rattail-demo",
version = __version__,
author = "Lance Edgar",
author_email = "lance@edbob.org",
url = "https://rattailproject.org/",
description = "Rattail Software Demo",
long_description = README,
classifiers = [
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.7',
'Topic :: Office/Business',
'Topic :: Software Development :: Libraries :: Python Modules',
],
install_requires = requires,
packages = find_packages(),
entry_points = {
'paste.app_factory': [
'main = rattail_demo.web.app:main',
],
},
)

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/*')