remove old stacks

This commit is contained in:
2023-09-11 20:12:49 +02:00
parent 205eae2264
commit 4c850f6c0b
174 changed files with 30 additions and 13842 deletions

6
.gitignore vendored
View File

@@ -1 +1,5 @@
*.bundle
/target/
*.sqlite
*.sqlite-wal
*.sqlite-shm
*.sqlite-journal

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO trips_items (\n item_id, \n trip_id, \n pick, \n pack, \n ready,\n new,\n user_id\n ) SELECT \n item_id,\n $1 as trip_id,\n pick,\n false as pack,\n false as ready,\n false as new,\n user_id\n FROM trips_items\n WHERE trip_id = $2 AND user_id = $3",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "4017d92f0898c5046c4fbe1cd440ca73e5eb5d0794c679c9e5f05eb87d1defca"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO trips_items (\n item_id, \n trip_id, \n pick, \n pack, \n ready,\n new,\n user_id\n ) SELECT \n id as item_id,\n $1 as trip_id,\n false as pick,\n false as pack,\n false as ready,\n false as new,\n user_id\n FROM inventory_items\n WHERE user_id = $2",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "905a4518c657a01831fead855bad141d34f699c58b6aa5bee492b6eef2115d74"
}

View File

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1 @@
-- Add migration script here

View File

@@ -1,7 +0,0 @@
__pycache__/
/venv/
*.sqlite
*.sqlite3
*.bak
*.bundle
/*.sqlite

View File

@@ -1,42 +0,0 @@
# ideas
Trip info:
* Itinerary
* Members
---
Item info:
Comments
Links
n:1 Product
---
Item groups that can be selected together. 1:1 product.
---
State management.
---
Item comments per trip (that are added to item overview page)
"This item was already on the following trips:"
---
Todos, with preparation time window.
---
Review when setting to "done", review per item and for the whole trip.
"Would take again" (default yes)
# todos
Category CRUD

View File

@@ -1 +0,0 @@
Single-database configuration for Flask.

View File

@@ -1,50 +0,0 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic,flask_migrate
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[logger_flask_migrate]
level = INFO
handlers =
qualname = flask_migrate
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

View File

@@ -1,91 +0,0 @@
from __future__ import with_statement
import logging
from logging.config import fileConfig
from flask import current_app
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
config.set_main_option(
'sqlalchemy.url',
str(current_app.extensions['migrate'].db.get_engine().url).replace(
'%', '%%'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
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.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True
)
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.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
connectable = current_app.extensions['migrate'].db.get_engine()
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

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

View File

@@ -1,34 +0,0 @@
"""empty message
Revision ID: 4e1894be1cca
Revises: 8593f77d68a3
Create Date: 2022-09-21 16:13:06.405648
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '4e1894be1cca'
down_revision = '8593f77d68a3'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('trip_to_triptype')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('trip_to_triptype',
sa.Column('trip_id', sa.VARCHAR(length=36), nullable=False),
sa.Column('trip_type_id', sa.VARCHAR(length=36), nullable=False),
sa.ForeignKeyConstraint(['trip_id'], ['trips.id'], ),
sa.ForeignKeyConstraint(['trip_type_id'], ['triptypes.id'], ),
sa.PrimaryKeyConstraint('trip_id', 'trip_type_id')
)
# ### end Alembic commands ###

View File

@@ -1,34 +0,0 @@
"""empty message
Revision ID: 8593f77d68a3
Revises:
Create Date: 2022-09-21 16:11:14.063997
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '8593f77d68a3'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('trip_to_triptype')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('trip_to_triptype',
sa.Column('trip_id', sa.VARCHAR(length=36), nullable=False),
sa.Column('trip_type_id', sa.VARCHAR(length=36), nullable=False),
sa.ForeignKeyConstraint(['trip_id'], ['trips.id'], ),
sa.ForeignKeyConstraint(['trip_type_id'], ['triptypes.id'], ),
sa.PrimaryKeyConstraint('trip_id', 'trip_type_id')
)
# ### end Alembic commands ###

View File

@@ -1,21 +0,0 @@
import uuid
import sqlalchemy
import csv
from flask import Flask
from flask_migrate import Migrate
from .helpers import *
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{app.root_path}/../items.sqlite"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
migrate = Migrate(app, db, render_as_batch=True)
from packager.models import *
import packager.views
db.create_all()

View File

@@ -1,55 +0,0 @@
import os
import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
class Base:
def __init__(self, element, root_path, active_page=None):
doc = dominate.document(title="Packager")
with doc.head:
t.script(src="https://unpkg.com/htmx.org@1.7.0")
t.script(src="https://cdn.tailwindcss.com")
t.script(src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.js", defer=True)
t.link(
rel="stylesheet",
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css",
)
with doc:
t.script(raw(open(os.path.join(root_path, "js/app.js")).read()))
with t.header(
_class=cls(
"bg-gray-200",
"p-5",
"flex",
"flex-row",
"flex-nowrap",
"justify-between",
"items-center",
)
):
t.span("Packager", _class=cls("text-xl", "font-semibold"))
with t.nav(
_class=cls("grow", "flex", "flex-row", "justify-center", "gap-x-6")
):
basecls = ["text-lg"]
activecls = ["font-bold", "underline"]
t.a(
"Inventory",
href="/inventory/",
_class=cls(
*basecls, *(activecls if active_page == "inventory" else [])
),
)
t.a(
"Trips",
href="/trips/",
_class=cls(
*basecls, *(activecls if active_page == "trips" else [])
),
)
doc.add(element.doc)
self.doc = doc

View File

@@ -1,18 +0,0 @@
import os
import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
class Home:
def __init__(self):
with t.div(id="home", _class=cls("p-8", "max-w-xl")) as doc:
with t.p():
t.a("Inventory", href="/inventory/")
with t.p():
t.a("Trips", href="/trips/")
self.doc = doc

View File

@@ -1,85 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
def InventoryCategoryList(categories):
with t.div() as doc:
t.h1("Categories", _class=cls("text-2xl", "mb-5"))
with t.table(
_class=cls(
"table",
"table-auto",
"border-collapse",
"border-spacing-0",
"border",
"w-full",
)
):
with t.thead(_class=cls("bg-gray-200")):
t.tr(
t.th("Name", _class=cls("border", "p-2")),
t.th("Weight", _class=cls("border", "p-2")),
_class="h-10",
)
with t.tbody() as b:
biggest_category_weight = max(
[sum([i.weight for i in c.items]) for c in categories] or [0]
)
for category in categories:
with t.tr(
_class=cls("h-10", "hover:bg-purple-100", "m-3", "h-full")
) as doc:
with t.td(
_class=cls(
"border",
"p-0",
"m-0",
*["font-bold"] if category.active else [],
)
):
t.a(
category.name,
id="select-category",
# data_hx_post=f"/list/{pkglist.id}/edit",
href=f"/inventory/category/{category.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
_class=cls("inline-block", "p-2", "m-0", "w-full"),
)
with t.td(
_class=cls("border", "p-0", "m-0"),
style="position:relative;",
):
with t.a(
id="select-category",
# data_hx_post=f"/list/{pkglist.id}/edit",
href=f"/inventory/category/{category.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
_class=cls("inline-block", "p-2", "m-0", "w-full"),
):
weight = sum([i.weight for i in category.items])
width = int(weight / biggest_category_weight * 100)
t.p(weight)
t.div(
_class=cls("bg-blue-600", "h-1.5"),
style=f"width: {width}%;position:absolute;left:0;bottom:0;right:0;",
)
# t.progress(max=biggest_category_weight, value=weight)
with t.tr(
_class=cls(
"h-10", "hover:bg-purple-200", "bg-gray-300", "font-bold"
)
) as doc:
with t.td(_class=cls("border", "p-0", "m-0")):
t.a("Sum", _class=cls("block", "p-2", "m-2"))
with t.td(_class=cls("border", "p-0", "m-0")):
t.a(
sum([sum([i.weight for i in c.items]) for c in categories]),
_class=cls("block", "p-2", "m-2"),
)
return doc

View File

@@ -1,143 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
import urllib
class InventoryItemInfoEditRow:
def __init__(self, baseurl, name, value, attribute, inputtype="text"):
with t.tr() as doc:
t.form(
id=f"edit-{attribute}",
action=urllib.parse.urljoin(baseurl, f"edit/{attribute}/submit/"),
target="_self",
method="post",
# data_hx_post=f"/list/{pkglist.id}/edit/submit",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
)
with t.tr(_class=cls("h-full")):
t.td(name, _class=cls("border", "p-2", "h-full"))
with t.td(_class=cls("border", "p-0")):
t._input(
_class=cls("bg-blue-200", "w-full", "h-full", "px-2"),
type=inputtype,
id="item-edit-name",
form=f"edit-{attribute}",
name=attribute,
value=value,
)
with t.td(
_class=cls(
"border",
"bg-red-200",
"hover:bg-red-400",
"cursor-pointer",
"w-8",
"text-center",
),
id=f"edit-{attribute}-abort",
):
with t.a(href=baseurl):
with t.button():
t.span(_class=cls("mdi", "mdi-cancel", "text-xl")),
with t.td(
id=f"edit-{attribute}-save",
_class=cls(
"border",
"bg-green-200",
"hover:bg-green-400",
"cursor-pointer",
"w-8",
"text-center",
),
):
with t.button(type="submit", form=f"edit-{attribute}"):
t.span(_class=cls("mdi", "mdi-content-save", "text-xl")),
self.doc = doc
class InventoryItemInfoNormalRow:
def __init__(self, editable, baseurl, name, value, attribute):
with t.tr() as doc:
t.td(name, _class=cls("border", "p-2"))
t.td(value, _class=cls("border", "p-2"))
if editable:
with t.td(
_class=cls(
"border",
"bg-blue-200",
"hover:bg-blue-400",
"cursor-pointer",
"w-8",
"text-center",
)
):
if editable:
with t.a(
# data_hx_post=f"/item/{item.id}/edit",
href=f"?edit={attribute}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
):
with t.button():
t.span(_class=cls("mdi", "mdi-pencil", "text-xl")),
self.doc = doc
class InventoryItemInfo:
def __init__(self, item, edit, baseurl):
with t.table(
id="trip-table",
_class=cls(
"table",
"table-auto",
"border-collapse",
"border-spacing-0",
"border",
"w-full",
),
) as doc:
with t.tbody() as b:
if edit == "name":
InventoryItemInfoEditRow(baseurl, "Name", item.name, "name")
else:
InventoryItemInfoNormalRow(
True, baseurl, "Name", item.name, "name"
)
if edit == "weight":
InventoryItemInfoEditRow(
baseurl,
"Weight",
str(item.weight),
"weight",
inputtype="number",
)
else:
InventoryItemInfoNormalRow(
True, baseurl, "Weight", str(item.weight), "weight"
)
self.doc = doc
class InventoryItemDetails:
def __init__(
self,
item, edit, baseurl
):
with t.div(_class=cls("p-8")
) as doc:
t.h1("Item", _class=cls("text-2xl", "font-semibold"))
with t.div(_class=cls("my-6")):
InventoryItemInfo(item, edit, baseurl)
self.doc = doc

View File

@@ -1,194 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
class InventoryItemRowEdit(object):
def __init__(self, item, biggest_item_weight):
with t.tr(_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-100")) as doc:
with t.td(colspan=2, _class=cls("border-none", "bg-purple-100", "h-10")):
with t.div():
t.form(
id="edit-item",
action=f"/inventory/item/{item.id}/edit/submit/",
target="_self",
method="post",
# data_hx_post=f"/list/{pkglist.id}/edit/submit",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
)
with t.div(_class=cls("flex", "flex-row", "h-full")):
with t.span(
_class=cls(
"border",
"border-1",
"border-purple-500",
"bg-purple-100",
"mr-1",
)
):
t._input(
_class=cls("bg-purple-100", "w-full", "h-full", "px-2"),
type="text",
id="item-edit-name",
form="edit-item",
name="name",
value=item.name,
**{
"x-on:input": "edit_submit_enabled = $event.srcElement.value.trim().length !== 0;"
},
)
with t.span(
_class=cls(
"border", "border-1", "border-purple-500", "bg-purple-100"
)
):
t._input(
_class=cls("bg-purple-100", "w-full", "h-full", "px-2"),
type="text",
id="item-edit-weight",
name="weight",
form="edit-item",
value=item.weight,
)
with t.td(
_class=cls(
"border",
"bg-red-200",
"hover:bg-red-400",
"cursor-pointer",
"w-8",
"text-center",
),
id="edit-item-abort",
):
with t.a(
href=f"/inventory/category/{item.category.id}",
# data_hx_post=f"/item/{item.id}/edit/cancel",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
):
with t.button():
t.span(_class=cls("mdi", "mdi-cancel", "text-xl")),
with t.td(
id="edit-item-save",
_class=cls(
"border",
"bg-green-200",
"hover:bg-green-400",
"cursor-pointer",
"w-8",
"text-center",
),
# **{
# "x-bind:class": 'edit_submit_enabled || "cursor-not-allowed opacity-50"',
# "x-on:htmx:before-request": "(e) => edit_submit_enabled || e.preventDefault()",
# },
):
with t.button(type="submit", form="edit-item"):
t.span(_class=cls("mdi", "mdi-content-save", "text-xl")),
self.doc = doc
class InventoryItemRowNormal(object):
def __init__(self, item, biggest_item_weight):
with t.tr(_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-100")) as doc:
with t.td(_class=cls("border", "p-0", "m-0")):
t.a(
item.name,
href=f"/inventory/item/{item.id}/",
_class=cls("p-2", "w-full", "inline-block"),
)
width = int(item.weight / biggest_item_weight * 100)
with t.td(_class=cls("border", "px-2"), style="position:relative;"):
t.p(str(item.weight))
t.div(
_class=cls("bg-blue-600", "h-1.5"),
style=";".join(
[
f"width: {width}%",
"position:absolute",
"left:0",
"bottom:0",
"right:0",
]
),
)
with t.td(
_class=cls(
"border",
"bg-blue-200",
"hover:bg-blue-400",
"cursor-pointer",
"w-8",
"text-center",
)
):
with t.a(
# data_hx_post=f"/item/{item.id}/edit",
href=f"?edit={item.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
id="start-edit-item",
):
with t.button():
t.span(_class=cls("mdi", "mdi-pencil", "text-xl")),
with t.td(
_class=cls(
"border",
"bg-red-200",
"hover:bg-red-400",
"cursor-pointer",
"w-8",
"text-center",
)
):
with t.a(
# data_hx_delete=f"/item/{item.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
href=f"/inventory/item/{item.id}/delete"
):
with t.button():
t.span(_class=cls("mdi", "mdi-delete", "text-xl"))
self.doc = doc
class InventoryItemRow(object):
def __init__(self, item, biggest_item_weight):
if item.edit:
self.doc = InventoryItemRowEdit(item, biggest_item_weight)
else:
self.doc = InventoryItemRowNormal(item, biggest_item_weight)
def InventoryItemList(items):
with t.div() as doc:
t.h1("Items", _class=cls("text-2xl", "mb-5"))
with t.table(
id="item-table",
_class=cls(
"table",
"table-auto",
"border-collapse",
"border-spacing-0",
"border",
"w-full",
),
):
with t.thead(_class=cls("bg-gray-200")):
t.tr(
t.th("Name", _class=cls("border", "p-2")),
t.th("Weight", _class=cls("border", "p-2")),
_class="h-10",
)
with t.tbody() as b:
biggest_item_weight = max([i.weight for i in items] or [0])
if biggest_item_weight <= 0:
biggest_item_weight = 1
for item in items:
InventoryItemRow(item, biggest_item_weight).doc
return doc

View File

@@ -1,33 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from .InventoryItemList import InventoryItemList
from .InventoryCategoryList import InventoryCategoryList
from .InventoryNewItem import InventoryNewItem
from ..helpers import *
class InventoryItemManager:
def __init__(
self,
categories,
items,
active_category,
name=None,
description=None,
error=False,
errormsg=None,
):
assert not (error and not errormsg)
with t.div(
id="pkglist-item-manager", _class=cls("p-8", "grid", "grid-cols-4", "gap-3")
) as doc:
with t.div(_class=cls("col-span-2")):
InventoryCategoryList(categories),
with t.div(_class=cls("col-span-2")):
InventoryItemList(items),
InventoryNewItem(categories, active_category)
self.doc = doc

View File

@@ -1,152 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
def InventoryNewItem(categories, active_category, name=None, weight=None):
with t.form(
id="new-item",
name="new_item",
# data_hx_post="/list/",
# data_hx_target="#item-manager",
# data_hx_swap="outerHTML",
action="/inventory/item/",
target="_self",
method="post",
_class=cls("mt-8", "p-5", "border-2", "border-gray-200"),
# **{
# "x-on:htmx:before-request": "(e) => submit_enabled || e.preventDefault()",
# "x-data": alpinedata(
# {
# "submit_enabled": (
# jsbool(not error)
# + '&& document.getElementById("listname").value.trim().length !== 0'
# )
# }
# ),
# },
) as doc:
with t.div(_class=cls("mb-5", "flex", "flex-row", "items-center")):
t.span(_class=cls("mdi", "mdi-playlist-plus", "text-2xl", "mr-4"))
t.p("Add new item", _class=cls("inline", "text-xl"))
with t.div(_class=cls("w-11/12", "mx-auto")):
with t.div(_class=cls("pb-8")):
with t.div(
_class=cls("flex", "flex-row", "justify-center", "items-start")
):
t.label(
"Name",
_for="item-name",
_class=cls("font-bold", "w-1/2", "p-2", "text-center"),
)
with t.span(_class=cls("w-1/2")):
t._input(
type="text",
id="item-name",
name="name",
**{"value": name} if name is not None else {},
# data_hx_target="#new-item",
# data_hx_post="/item/name/validate",
# data_hx_swap="outerHTML",
# data_hx_trigger="changed",
_class=cls(
"block",
"w-full",
"p-2",
"bg-gray-50",
# "appearance-none" if error else None,
"border-2",
# "border-red-400" if error else "border-gray-300",
"rounded",
"focus:outline-none",
"focus:bg-white",
# "focus:border-purple-500" if not error else None,
),
# **{
# "x-on:input": "submit_enabled = $event.srcElement.value.trim().length !== 0;"
# },
)
# t.p(
# errormsg, _class=cls("mt-1", "text-red-400", "text-sm")
# ) if error else None
with t.div(
_class=cls("flex", "flex-row", "justify-center", "items-center", "pb-8")
):
t.label(
"Weight",
_for="item-weight",
_class=cls("font-bold", "w-1/2", "text-center"),
)
with t.span(_class=cls("w-1/2")):
t._input(
type="text",
id="item-weight",
name="weight",
**{"value": weight} if weight is not None else {},
_class=cls(
"block",
"w-full",
"p-2",
"bg-gray-50",
"appearance-none",
"border-2",
"border-gray-300",
"rounded",
"focus:outline-none",
"focus:bg-white",
"focus:border-purple-500",
),
)
with t.div(
_class=cls("flex", "flex-row", "justify-center", "items-center", "pb-8")
):
t.label(
"Category",
_for="item-category",
_class=cls("font-bold", "w-1/2", "text-center"),
)
with t.span(_class=cls("w-1/2")):
with t.select(
id="item-category",
name="category",
_class=cls(
"block",
"w-full",
"p-2",
"bg-gray-50",
# "appearance-none",
"border-2",
"border-gray-300",
"rounded",
"focus:outline-none",
"focus:bg-white",
"focus:border-purple-500",
),
):
for category in categories:
if active_category and category.id == active_category.id:
t.option(
category.name, value=category.id, selected=True
)
else:
t.option(category.name, value=category.id)
t._input(
type="submit",
value="Add",
# **{
# "x-bind:class": 'submit_enabled ? "cursor-pointer" : "cursor-not-allowed opacity-50"'
# },
_class=cls(
"py-2",
"border-2",
"rounded",
"border-gray-300",
"mx-auto",
"w-full",
# "hover:border-purple-500" if not error else None,
# "hover:bg-purple-200" if not error else None,
),
)
return doc

View File

@@ -1,145 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
def NewTrip(name=None):
with t.form(
id="new-trip",
name="new_trip",
# data_hx_post="/list/",
# data_hx_target="#trip-manager",
# data_hx_swap="outerHTML",
action="/trip/",
target="_self",
method="post",
_class=cls("mt-8", "p-5", "border-2", "border-gray-200"),
# **{
# "x-on:htmx:before-request": "(e) => submit_enabled || e.preventDefault()",
# "x-data": alpinedata(
# {
# "submit_enabled": (
# jsbool(not error)
# + '&& document.getElementById("listname").value.trim().length !== 0'
# )
# }
# ),
# },
) as doc:
with t.div(_class=cls("mb-5", "flex", "flex-row", "trips-center")):
t.span(_class=cls("mdi", "mdi-playlist-plus", "text-2xl", "mr-4"))
t.p("Add new trip", _class=cls("inline", "text-xl"))
with t.div(_class=cls("w-11/12", "mx-auto")):
with t.div(_class=cls("pb-8")):
with t.div(
_class=cls("flex", "flex-row", "justify-center", "trips-start")
):
t.label(
"Name",
_for="trip-name",
_class=cls("font-bold", "w-1/2", "p-2", "text-center"),
)
with t.span(_class=cls("w-1/2")):
t._input(
type="text",
id="trip-name",
name="name",
**{"value": name} if name is not None else {},
# data_hx_target="#new-trip",
# data_hx_post="/trip/name/validate",
# data_hx_swap="outerHTML",
# data_hx_trigger="changed",
_class=cls(
"block",
"w-full",
"p-2",
"bg-gray-50",
# "appearance-none" if error else None,
"border-2",
# "border-red-400" if error else "border-gray-300",
"rounded",
"focus:outline-none",
"focus:bg-white",
# "focus:border-purple-500" if not error else None,
),
# **{
# "x-on:input": "submit_enabled = $event.srcElement.value.trim().length !== 0;"
# },
)
# t.p(
# errormsg, _class=cls("mt-1", "text-red-400", "text-sm")
# ) if error else None
with t.div(
_class=cls("flex", "flex-row", "justify-center", "trips-center", "pb-8")
):
t.label(
"Start date",
_for="start-date",
_class=cls("font-bold", "w-1/2", "text-center"),
)
with t.span(_class=cls("w-1/2")):
t._input(
type="date",
id="start-date",
name="start-date",
_class=cls(
"block",
"w-full",
"p-2",
"bg-gray-50",
"appearance-none",
"border-2",
"border-gray-300",
"rounded",
"focus:outline-none",
"focus:bg-white",
"focus:border-purple-500",
),
)
with t.div(
_class=cls("flex", "flex-row", "justify-center", "trips-center", "pb-8")
):
t.label(
"End date",
_for="end-date",
_class=cls("font-bold", "w-1/2", "text-center"),
)
with t.span(_class=cls("w-1/2")):
t._input(
type="date",
id="end-date",
name="end-date",
_class=cls(
"block",
"w-full",
"p-2",
"bg-gray-50",
"appearance-none",
"border-2",
"border-gray-300",
"rounded",
"focus:outline-none",
"focus:bg-white",
"focus:border-purple-500",
),
)
t._input(
type="submit",
value="Add",
# **{
# "x-bind:class": 'submit_enabled ? "cursor-pointer" : "cursor-not-allowed opacity-50"'
# },
_class=cls(
"py-2",
"border-2",
"rounded",
"border-gray-300",
"mx-auto",
"w-full",
# "hover:border-purple-500" if not error else None,
# "hover:bg-purple-200" if not error else None,
),
)
return doc

View File

@@ -1,134 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
class TripCategoryList:
def __init__(self, trip, categories):
with t.div() as doc:
t.h1("Categories", _class=cls("text-xl", "mb-5"))
with t.table(
_class=cls(
"table",
"table-auto",
"border-collapse",
"border-spacing-0",
"border",
"w-full",
)
):
with t.thead(_class=cls("bg-gray-200")):
t.tr(
t.th("Name", _class=cls("border", "p-2")),
t.th("Weight", _class=cls("border", "p-2")),
t.th("Max", _class=cls("border", "p-2")),
_class="h-10",
)
with t.tbody() as b:
for category in categories:
items = [
i
for i in trip.items
if i.inventory_item.category_id == category.id
]
biggest_category_weight = 1
for cat in categories:
category_items = [
i
for i in trip.items
if i.inventory_item.category_id == cat.id
]
weight_sum = sum(
[
i.inventory_item.weight
for i in category_items
if i.pick
]
)
if weight_sum > biggest_category_weight:
biggest_category_weight = weight_sum
weight = sum([i.inventory_item.weight for i in items if i.pick])
with t.tr(
_class=cls(
"h-10",
"hover:bg-purple-100",
"m-3",
"h-full",
*["bg-blue-100"]
if category.active
else (
["bg-red-100"]
if any([i.pick != i.pack for i in items])
else []
),
)
) as doc:
with t.td(
_class=cls(
"border",
"p-0",
"m-0",
*["font-bold"] if category.active else [],
)
):
t.a(
category.name,
id="select-category",
# data_hx_post=f"/list/{pkglist.id}/edit",
href=f"/trip/{trip.id}/category/{category.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
_class=cls("inline-block", "p-2", "m-0", "w-full"),
)
with t.td(
_class=cls("border", "p-0", "m-0"),
style="position:relative;",
):
with t.a(
id="select-category",
# data_hx_post=f"/list/{pkglist.id}/edit",
href=f"/category/{category.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
_class=cls("inline-block", "p-2", "m-0", "w-full"),
):
width = int(weight / biggest_category_weight * 100)
t.p(weight)
t.div(
_class=cls("bg-blue-600", "h-1.5"),
style=f"width: {width}%;position:absolute;left:0;bottom:0;right:0;",
)
with t.td(_class=cls("border", "p-0", "m-0")):
t.a(
sum([i.inventory_item.weight for i in items]),
id="select-category",
# data_hx_post=f"/list/{pkglist.id}/edit",
href=f"/category/{category.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
_class=cls("inline-block", "p-2", "m-0", "w-full"),
)
# with t.tr(_class=cls("h-10", "hover:bg-purple-200", "bg-gray-300", "font-bold")) as doc:
# with t.td(_class=cls("border", "p-0", "m-0")):
# t.a(
# "Sum",
# _class=cls("block", "p-2", "m-2"),
# )
# with t.td(_class=cls("border", "p-0", "m-0")):
# t.a(
# sum(([i.inventory_item.weight for i in c.items]) for c in categories]),
# _class=cls("block", "p-2", "m-2"),
# )
# with t.td(_class=cls("border", "p-0", "m-0", "font-normal")):
# t.a(
# sum(([i.inventory_item.weight for i in c.items]) for c in categories]),
# _class=cls("block", "p-2", "m-2"),
# )
self.doc = doc

View File

@@ -1,266 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
class TripItemRowEdit(object):
def __init__(self, item, biggest_item_weight):
with t.tr(_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-100")) as doc:
with t.td(_class=cls("border")):
with t.a(
id="select-category",
# data_hx_post=f"/list/{pkglist.id}/edit",
href=f"?item_{'unpick' if item.pick else 'pick'}={item.inventory_item.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
_class=cls(
"inline-block",
"p-2",
"m-0",
"w-full",
"flex",
"justify-center",
"content-center",
),
):
t._input(
type="checkbox", **({"checked": True} if item.pick else {})
)
with t.td(_class=cls("border")):
with t.a(
id="select-category",
# data_hx_post=f"/list/{pkglist.id}/edit",
href=f"?item_{'unpack' if item.pack else 'pack'}={item.inventory_item.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
_class=cls(
"inline-block",
"p-2",
"m-0",
"w-full",
"flex",
"justify-center",
"content-center",
),
):
t._input(
type="checkbox", **({"checked": True} if item.pack else {})
)
# with t.td(item.name, _class=cls("border", "px-2")),
with t.td(colspan=2, _class=cls("border-none", "bg-purple-100", "h-10")):
with t.div():
t.form(
id="edit-item",
action=f"/inventory/item/{item.inventory_item.id}/edit/submit/",
target="_self",
method="post",
# data_hx_post=f"/list/{pkglist.id}/edit/submit",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
)
with t.div(_class=cls("flex", "flex-row", "h-full")):
with t.span(
_class=cls(
"border",
"border-1",
"border-purple-500",
"bg-purple-100",
"mr-1",
)
):
t._input(
_class=cls("bg-purple-100", "w-full", "h-full", "px-2"),
type="text",
id="item-edit-name",
form="edit-item",
name="name",
value=item.name,
**{
"x-on:input": "edit_submit_enabled = $event.srcElement.value.trim().length !== 0;"
},
)
with t.span(
_class=cls(
"border", "border-1", "border-purple-500", "bg-purple-100"
)
):
t._input(
_class=cls("bg-purple-100", "w-full", "h-full", "px-2"),
type="text",
id="item-edit-weight",
name="weight",
form="edit-item",
value=item.weight,
)
with t.td(
_class=cls(
"border",
"bg-red-200",
"hover:bg-red-400",
"cursor-pointer",
"w-8",
"text-center",
),
id="edit-item-abort",
):
with t.a(
href=f"/inventory/category/{item.category.id}",
# data_hx_post=f"/item/{item.id}/edit/cancel",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
):
with t.button():
t.span(_class=cls("mdi", "mdi-cancel", "text-xl")),
with t.td(
id="edit-item-save",
_class=cls(
"border",
"bg-green-200",
"hover:bg-green-400",
"cursor-pointer",
"w-8",
"text-center",
),
# **{
# "x-bind:class": 'edit_submit_enabled || "cursor-not-allowed opacity-50"',
# "x-on:htmx:before-request": "(e) => edit_submit_enabled || e.preventDefault()",
# },
):
with t.button(type="submit", form="edit-item"):
t.span(_class=cls("mdi", "mdi-content-save", "text-xl")),
self.doc = doc
class TripItemRowNormal(object):
def __init__(self, item, biggest_item_weight):
with t.tr(_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-100")) as doc:
with t.td(_class=cls("border")):
with t.a(
id="select-category",
# data_hx_post=f"/list/{pkglist.id}/edit",
href=f"?item_{'unpick' if item.pick else 'pick'}={item.inventory_item.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
_class=cls(
"inline-block",
"p-2",
"m-0",
"w-full",
"justify-center",
"content-center",
"flex",
),
):
t._input(
type="checkbox",
form=f"toggle-item-pick",
name="pick-{item.inventory_item.id}",
**({"checked": True} if item.pick else {}),
) # , xstyle="position: relative;z-index: 1;pointer-events: auto; ")
with t.td(_class=cls("border")):
with t.a(
id="select-category",
# data_hx_post=f"/list/{pkglist.id}/edit",
href=f"?item_{'unpack' if item.pack else 'pack'}={item.inventory_item.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
_class=cls(
"inline-block",
"p-2",
"m-0",
"w-full",
"flex",
"justify-center",
"content-center",
),
):
t._input(
type="checkbox",
form=f"toggle-item-pack",
name="pack-{item.inventory_item.id}",
**({"checked": True} if item.pack else {}),
) # , xstyle="position: relative;z-index: 1;pointer-events: auto; ")
t.td(
item.inventory_item.name,
_class=cls(
"border",
"px-2",
*(("bg-red-100",) if item.pick != item.pack else ()),
),
),
width = int(item.inventory_item.weight / biggest_item_weight * 100)
with t.td(_class=cls("border", "px-2"), style="position:relative;"):
t.p(str(item.inventory_item.weight))
t.div(
_class=cls("bg-blue-600", "h-1.5"),
style=";".join(
[
f"width: {width}%",
"position:absolute",
"left:0",
"bottom:0",
"right:0",
]
),
)
self.doc = doc
class TripItemRow(object):
def __init__(self, item, biggest_item_weight):
if item.edit:
self.doc = TripItemRowEdit(item, biggest_item_weight)
else:
self.doc = TripItemRowNormal(item, biggest_item_weight)
class TripItemList:
def __init__(self, trip, active_category):
with t.div() as doc:
t.h1("Items", _class=cls("text-xl", "mb-5"))
with t.table(
id="item-table",
_class=cls(
"table",
"table-auto",
"border-collapse",
"border-spacing-0",
"border",
"w-full",
),
):
with t.thead(_class=cls("bg-gray-200")):
t.tr(
t.th("Take?", _class=cls("border", "p-2", "w-0")),
t.th("Packed?", _class=cls("border", "p-2", "w-0")),
t.th("Name", _class=cls("border", "p-2")),
t.th("Weight", _class=cls("border", "p-2")),
_class="h-10",
)
with t.tbody() as b:
if active_category:
biggest_item_weight = max(
[
i.inventory_item.weight
for i in trip.items
if i.inventory_item.category_id == active_category.id
]
or [0]
)
else:
biggest_item_weight = max(
[i.inventory_item.weight for i in trip.items] or [0]
)
if biggest_item_weight <= 0:
biggest_item_weight = 1
print(active_category)
for item in trip.items:
if (
not active_category
or item.inventory_item.category_id == active_category.id
):
TripItemRow(item, biggest_item_weight).doc
self.doc = doc

View File

@@ -1,21 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from .TripCategoryList import TripCategoryList
from .TripItemList import TripItemList
from ..helpers import *
class TripItemManager:
def __init__(self, trip, categories, active_category):
with t.div(
id="pkglist-item-manager", _class=cls("grid", "grid-cols-4", "gap-3")
) as doc:
with t.div(_class=cls("col-span-2")):
TripCategoryList(trip, categories),
with t.div(_class=cls("col-span-2")):
TripItemList(trip, active_category=active_category),
self.doc = doc

View File

@@ -1,19 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from .TripTable import TripTable
from .NewTrip import NewTrip
from ..helpers import *
class TripList:
def __init__(self, trips):
with t.div(id="trips-manager", _class=cls("p-8")) as doc:
TripTable(trips),
NewTrip(
# name=name, description=description, error=error, errormsg=errormsg
)
self.doc = doc

View File

@@ -1,381 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
import urllib
import decimal
from .TripTable import TripTable
from .NewTrip import NewTrip
from .TripItemManager import TripItemManager
from ..helpers import *
from ..models import TripState
class TripInfoEditRow:
def __init__(self, baseurl, name, value, attribute, inputtype="text"):
with t.tr() as doc:
t.form(
id=f"edit-{attribute}",
action=urllib.parse.urljoin(baseurl, f"edit/{attribute}/submit/"),
target="_self",
method="post",
# data_hx_post=f"/list/{pkglist.id}/edit/submit",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
)
with t.tr(_class=cls("h-full")):
t.td(name, _class=cls("border", "p-2", "h-full"))
with t.td(_class=cls("border", "p-0")):
t._input(
_class=cls("bg-blue-200", "w-full", "h-full", "px-2"),
type=inputtype,
id="item-edit-name",
form=f"edit-{attribute}",
name=attribute,
value=value,
)
with t.td(
_class=cls(
"border",
"bg-red-200",
"hover:bg-red-400",
"cursor-pointer",
"w-8",
"text-center",
),
id=f"edit-{attribute}-abort",
):
with t.a(href=baseurl):
with t.button():
t.span(_class=cls("mdi", "mdi-cancel", "text-xl")),
with t.td(
id=f"edit-{attribute}-save",
_class=cls(
"border",
"bg-green-200",
"hover:bg-green-400",
"cursor-pointer",
"w-8",
"text-center",
),
):
with t.button(type="submit", form=f"edit-{attribute}"):
t.span(_class=cls("mdi", "mdi-content-save", "text-xl")),
self.doc = doc
class TripInfoNormalRow:
def __init__(self, editable, baseurl, name, value, attribute):
with t.tr() as doc:
t.td(name, _class=cls("border", "p-2"))
t.td(value, _class=cls("border", "p-2"))
if editable:
with t.td(
_class=cls(
"border",
"bg-blue-200",
"hover:bg-blue-400",
"cursor-pointer",
"w-8",
"text-center",
)
):
if editable:
with t.a(
# data_hx_post=f"/item/{item.id}/edit",
href=f"?edit={attribute}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
):
with t.button():
t.span(_class=cls("mdi", "mdi-pencil", "text-xl")),
self.doc = doc
class TripInfo:
def __init__(self, trip, edit, baseurl, triptypes):
with t.table(
id="trip-table",
_class=cls(
"table",
"table-auto",
"border-collapse",
"border-spacing-0",
"border",
"w-full",
),
) as doc:
with t.tbody() as b:
TripInfoNormalRow(False, "", "State", trip.state.name, "")
if edit == "location":
TripInfoEditRow(baseurl, "Location", trip.location, "location")
else:
TripInfoNormalRow(
True, baseurl, "Location", trip.location, "location"
)
if edit == "start_date":
TripInfoEditRow(
baseurl,
"From",
str(trip.start_date),
"start_date",
inputtype="date",
)
else:
TripInfoNormalRow(
True, baseurl, "From", str(trip.start_date), "start_date"
)
if edit == "end_date":
TripInfoEditRow(
baseurl, "To", str(trip.end_date), "end_date", inputtype="date"
)
else:
TripInfoNormalRow(
True, baseurl, "To", str(trip.end_date), "end_date"
)
if edit == "temp_min":
TripInfoEditRow(
baseurl,
"Temp (min)",
trip.temp_min,
"temp_min",
inputtype="number",
)
else:
TripInfoNormalRow(
True, baseurl, "Temp (min)", trip.temp_min, "temp_min"
)
if edit == "temp_max":
TripInfoEditRow(
baseurl,
"Temp (max)",
trip.temp_max,
"temp_max",
inputtype="number",
)
else:
TripInfoNormalRow(
True, baseurl, "Temp (max)", trip.temp_max, "temp_max"
)
with t.tr():
t.td(f"Types", _class=cls("border", "p-2"))
with t.td(_class=cls("border", "p-2")):
with t.ul(
_class=cls(
"flex",
"flex-row",
"flex-wrap",
"gap-2",
"justify-between",
)
):
with t.div(
_class=cls(
"flex",
"flex-row",
"flex-wrap",
"gap-2",
"justify-start",
)
):
for triptype in trip.types:
with t.a(href=f"?type_remove={triptype.id}"):
with t.li(
_class=cls(
"border",
"rounded-2xl",
"py-0.5",
"px-2",
"bg-green-100",
"cursor-pointer",
"flex",
"flex-column",
"items-center",
"hover:bg-red-200",
"gap-1",
)
):
t.span(triptype.name)
t.span(
_class=cls(
"mdi", "mdi-delete", "text-sm"
)
)
with t.div(
_class=cls(
"flex",
"flex-row",
"flex-wrap",
"gap-2",
"justify-start",
)
):
for triptype in triptypes:
if triptype not in trip.types:
with t.a(href=f"?type_add={triptype.id}"):
with t.li(
_class=cls(
"border",
"rounded-2xl",
"py-0.5",
"px-2",
"bg-gray-100",
"cursor-pointer",
"flex",
"flex-column",
"items-center",
"hover:bg-green-200",
"gap-1",
"opacity-60",
)
):
t.span(triptype.name)
t.span(
_class=cls(
"mdi", "mdi-plus", "text-sm"
)
)
with t.tr():
t.td(f"Carried weight", _class=cls("border", "p-2"))
weight = sum(
[i.inventory_item.weight for i in trip.items if i.pick]
) / decimal.Decimal(1000)
t.td(f"{weight} kg", _class=cls("border", "p-2"))
self.doc = doc
class TripComments:
def __init__(self, trip, baseurl):
with t.div() as doc:
t.h1("Comments", _class=cls("text-xl", "mb-5"))
t.form(
id="edit-comment",
action=urllib.parse.urljoin(baseurl, f"edit/comment/submit/"),
target="_self",
method="post",
# data_hx_post=f"/list/{pkglist.id}/edit/submit",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
)
# https://stackoverflow.com/a/48460773
t.textarea(
trip.comment or "",
name="comment",
form="edit-comment",
_class=cls("border", "w-full", "h-48"),
oninput='this.style.height = "";this.style.height = this.scrollHeight + 2 + "px"',
)
with t.button(
type="submit",
form=f"edit-comment",
_class=cls(
"mt-2",
"border",
"bg-green-200",
"hover:bg-green-400",
"cursor-pointer",
"flex",
"flex-column",
"p-2",
"gap-2",
"items-center",
),
):
t.span(_class=cls("mdi", "mdi-content-save", "text-xl")),
t.span("Save")
self.doc = doc
class TripActions:
def __init__(self, trip):
with t.div() as doc:
t.h1("Actions", _class=cls("text-xl", "mb-5"))
with t.div(_class=cls("flex", "flex-column", "gap-2")):
if trip.state == TripState.Planning:
t.button("Finish planning", _class=cls("border", "p-2"))
if trip.state in (TripState.Planned, TripState.Active):
t.button("Start review", _class=cls("border", "p-2"))
if trip.state == TripState.Done:
t.button("Back to review", _class=cls("border", "p-2"))
self.doc = doc
class TripManager:
def __init__(self, trip, categories, active_category, edit, baseurl, triptypes):
with t.div(id="trips-manager", _class=cls("p-8")) as doc:
if edit == "name":
t.form(
id=f"edit-name",
action=urllib.parse.urljoin(baseurl, f"edit/name/submit/"),
target="_self",
method="post",
# data_hx_post=f"/list/{pkglist.id}/edit/submit",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
)
with t.div(_class=cls("flex", "flex-row", "items-center", "gap-x-3")):
t._input(
_class=cls("bg-blue-200", "w-full", "h-full", "px-2", "text-2xl", "font-semibold"),
type="text",
id="item-edit-name",
form=f"edit-name",
name="name",
value=trip.name,
)
with t.a(href=baseurl):
with t.button(_class=cls(
"bg-red-200",
"hover:bg-red-400",
"cursor-pointer")):
t.span(_class=cls("mdi", "mdi-cancel", "text-xl")),
with t.button(type="submit", form=f"edit-name", _class=cls(
"bg-green-200",
"hover:bg-green-400",
"cursor-pointer")):
t.span(_class=cls("mdi", "mdi-content-save", "text-xl")),
else:
with t.div(_class=cls("flex", "flex-row", "items-center", "gap-x-3")):
t.h1(trip.name, _class=cls("text-2xl", "font-semibold"))
with t.span():
with t.a(
# data_hx_post=f"/item/{item.id}/edit",
href=f"?edit=name",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
):
with t.button():
t.span(_class=cls("mdi", "mdi-pencil", "text-xl", "opacity-50")),
with t.div(_class=cls("my-6")):
TripInfo(trip, edit, baseurl, triptypes).doc
with t.div(_class=cls("my-6")):
TripComments(trip, baseurl).doc
with t.div(_class=cls("my-6")):
TripActions(trip).doc
with t.div(_class=cls("my-6")):
TripItemManager(
trip, categories=categories, active_category=active_category
).doc
self.doc = doc

View File

@@ -1,96 +0,0 @@
import datetime
import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
class TripRow(object):
def __init__(self, trip):
with t.tr(
_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-100", "h-full")
) as doc:
with t.td(_class=cls("border", "p-0", "m-0")):
t.a(
trip.name,
id="select-trip",
# data_hx_post=f"/list/{pkglist.id}/edit",
href=f"/trip/{trip.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
_class=cls("inline-block", "p-2", "m-0", "w-full"),
)
with t.td(_class=cls("border", "p-0", "m-0")):
t.a(
t.p(str(trip.start_date)),
id="select-trip",
# data_hx_post=f"/list/{pkglist.id}/edit",
href=f"/trip/{trip.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
_class=cls("inline-block", "p-2", "m-0", "w-full"),
)
with t.td(_class=cls("border", "p-0", "m-0")):
t.a(
t.p(str(trip.end_date)),
id="select-trip",
# data_hx_post=f"/list/{pkglist.id}/edit",
href=f"/trip/{trip.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
_class=cls("inline-block", "p-2", "m-0", "w-full"),
)
with t.td(_class=cls("border", "p-0", "m-0")):
t.a(
t.p((trip.end_date - trip.start_date).days),
id="select-trip",
# data_hx_post=f"/list/{pkglist.id}/edit",
href=f"/trip/{trip.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
_class=cls("inline-block", "p-2", "m-0", "w-full"),
)
today = datetime.datetime.now().date()
with t.td(_class=cls("border", "p-0", "m-0")):
t.a(
t.p(trip.state.name),
id="select-trip",
# data_hx_post=f"/list/{pkglist.id}/edit",
href=f"/trip/{trip.id}",
# data_hx_target="closest tr",
# data_hx_swap="outerHTML",
_class=cls("inline-block", "p-2", "m-0", "w-full"),
)
self.doc = doc
def TripTable(trips):
with t.div() as doc:
t.h1("Trips", _class=cls("text-2xl", "mb-5"))
with t.table(
id="trip-table",
_class=cls(
"table",
"table-auto",
"border-collapse",
"border-spacing-0",
"border",
"w-full",
),
):
with t.thead(_class=cls("bg-gray-200")):
t.tr(
t.th("Name", _class=cls("border", "p-2")),
t.th("From", _class=cls("border", "p-2")),
t.th("To", _class=cls("border", "p-2")),
t.th("Nights", _class=cls("border", "p-2")),
t.th("State", _class=cls("border", "p-2")),
_class="h-10",
)
with t.tbody() as b:
for trip in trips:
TripRow(trip).doc
return doc

View File

@@ -1,6 +0,0 @@
from .Base import Base
from .Home import Home
from .InventoryItemManager import InventoryItemManager
from .InventoryItemDetails import InventoryItemDetails
from .TripManager import TripManager
from .TripList import TripList

View File

@@ -1,14 +0,0 @@
def cls(*args):
return " ".join([a for a in args if a is not None])
def jsbool(b):
return "true" if b else "false"
def alpinedata(d):
elements = []
for k, v in d.items():
elements.append(f"{k}: " + v)
return "{" + ",".join(elements) + "}"

View File

@@ -1,3 +0,0 @@
document.body.addEventListener('htmx:responseError', function(evt) {
console.log(evt.detail);
});

View File

@@ -1,86 +0,0 @@
from . import db
import enum
class InventoryItemCategory(db.Model):
__tablename__ = "inventoryitemcategories"
id = db.Column(db.String(36), primary_key=True)
name = db.Column(db.Text, unique=True, nullable=False)
description = db.Column(db.Text)
items = db.relationship("InventoryItem", backref="category", lazy=True)
active = False
class InventoryItem(db.Model):
__tablename__ = "inventoryitems"
id = db.Column(db.String(36), primary_key=True)
name = db.Column(db.Text, unique=True, nullable=False)
description = db.Column(db.Text)
weight = db.Column(db.Integer, nullable=False)
category_id = db.Column(
db.String(36), db.ForeignKey("inventoryitemcategories.id"), nullable=False
)
edit = False
class TripItems(db.Model):
__tablename__ = "tripitems"
item_id = db.Column(
db.String(36),
db.ForeignKey("inventoryitems.id"),
nullable=False,
primary_key=True,
)
trip_id = db.Column(
db.String(36), db.ForeignKey("trips.id"), nullable=False, primary_key=True
)
inventory_item = db.relationship("InventoryItem", lazy=True)
pick = db.Column(db.Boolean, nullable=False)
pack = db.Column(db.Boolean, nullable=False)
edit = False
class TripType(db.Model):
__tablename__ = "triptypes"
id = db.Column(db.String(36), primary_key=True)
name = db.Column(db.Text, unique=True, nullable=False)
class TripToTripType(db.Model):
__tablename__ = "trips_to_triptypes"
trip_id = db.Column(
db.String(36), db.ForeignKey("trips.id"), nullable=False, primary_key=True
)
trip_type_id = db.Column(
db.String(36), db.ForeignKey("triptypes.id"), nullable=False, primary_key=True
)
class TripState(enum.Enum):
Planning = 1
Planned = 2
Active = 3
Review = 4
Done = 5
class Trip(db.Model):
__tablename__ = "trips"
id = db.Column(db.String(36), primary_key=True)
name = db.Column(db.Text, unique=True, nullable=False)
start_date = db.Column(db.Date, nullable=False)
end_date = db.Column(db.Date, nullable=False)
location = db.Column(db.Text, nullable=False)
temp_min = db.Column(db.Integer, nullable=False)
temp_max = db.Column(db.Integer, nullable=False)
comment = db.Column(db.Text, nullable=False)
types = db.relationship("TripType", secondary="trips_to_triptypes", lazy=True)
items = db.relationship("TripItems", lazy=True)
state = db.Column(db.Enum(TripState), nullable=False)

View File

@@ -1,574 +0,0 @@
import sqlalchemy
from . import app
from .models import *
from .helpers import *
import uuid
import os
import urllib
import datetime
import dominate
import dominate.tags as t
from dominate.util import raw
from .components import (
InventoryItemManager,
Base,
Home,
TripList,
TripManager,
InventoryItemDetails,
)
from flask import request, make_response
def get_categories():
return InventoryItemCategory.query.all()
def get_trips():
return Trip.query.all()
def get_all_items():
return InventoryItem.query.all()
def get_triptypes():
return TripType.query.all()
def get_items(category):
return InventoryItem.query.filter_by(category_id=str(category.id))
def get_item(id):
return InventoryItem.query.filter_by(id=str(id)).first()
def get_trip(id):
return Trip.query.filter_by(id=str(id)).first()
def pick_item(trip_id, item_id):
item = TripItems.query.filter_by(item_id=str(item_id), trip_id=str(trip_id)).first()
item.pick = True
db.session.commit()
def unpick_item(trip_id, item_id):
item = TripItems.query.filter_by(item_id=str(item_id), trip_id=str(trip_id)).first()
item.pick = False
db.session.commit()
def pack_item(trip_id, item_id):
item = TripItems.query.filter_by(item_id=str(item_id), trip_id=str(trip_id)).first()
item.pack = True
db.session.commit()
def unpack_item(trip_id, item_id):
item = TripItems.query.filter_by(item_id=str(item_id), trip_id=str(trip_id)).first()
item.pack = False
db.session.commit()
def add_item(name, weight, category_id):
db.session.add(
InventoryItem(
id=str(uuid.uuid4()),
name=name,
description="",
weight=weight,
category_id=category_id,
)
)
db.session.commit()
@app.route("/")
def root():
return make_response(Base(Home(), app.root_path).doc.render(), 200)
@app.route("/inventory/")
def inventory():
categories = get_categories()
items = get_all_items()
args = request.args.to_dict()
error = False
if not is_htmx():
edit = request.args.get("edit")
if edit is not None:
match = [i for i in items if i.id == edit]
if match:
match[0].edit = True
return make_response(
Base(
InventoryItemManager(categories, items, active_category=None),
app.root_path,
active_page="inventory",
).doc.render(),
200,
)
@app.route("/inventory/item/<uuid:id>/")
def inventory_item(id):
item = get_item(id)
args = request.args.to_dict()
edit = args.pop("edit", None)
return make_response(
Base(
InventoryItemDetails(item, edit=edit, baseurl=f"/inventory/item/{id}/",), app.root_path, active_page="inventory"
).doc.render(),
200,
)
@app.route(
"/inventory/item/<uuid:id>/edit/<string:attribute>/submit/",
methods=["POST"],
)
def edit_inventory_submit(id, attribute):
new_value = request.form[attribute]
if attribute in ("weight"):
new_value = int(new_value)
updates = InventoryItem.query.filter_by(id=str(id)).update({attribute: new_value})
db.session.commit()
if updates == 0:
# todo what to do without js?
return make_response("", 404)
redirect = request.path[: -(len(f"edit/{attribute}/submit/"))]
r = make_response("", 303)
r.headers["Location"] = redirect
return r
@app.route("/trips/")
def trips():
trips = get_trips()
return make_response(
Base(TripList(trips), app.root_path, active_page="trips").doc.render(), 200
)
@app.route("/trip/<uuid:id>/")
def trip(id):
args = request.args.to_dict()
item_to_pick = args.pop("item_pick", None)
if item_to_pick:
pick_item(id, item_to_pick)
r = make_response("", 303)
if args:
r.headers["Location"] = f"/trip/{id}/?" + urllib.parse.urlencode(params)
else:
r.headers["Location"] = f"/trip/{id}/"
return r
item_to_unpick = args.pop("item_unpick", None)
if item_to_unpick:
unpick_item(id, item_to_unpick)
r = make_response("", 303)
if args:
r.headers["Location"] = f"/trip/{id}/?" + urllib.parse.urlencode(params)
else:
r.headers["Location"] = f"/trip/{id}/"
return r
item_to_pack = args.pop("item_pack", None)
if item_to_pack:
pack_item(id, item_to_pack)
r = make_response("", 303)
if args:
r.headers["Location"] = f"/trip/{id}/?" + urllib.parse.urlencode(params)
else:
r.headers["Location"] = f"/trip/{id}/"
return r
item_to_unpack = args.pop("item_unpack", None)
if item_to_unpack:
unpack_item(id, item_to_unpack)
r = make_response("", 303)
if args:
r.headers["Location"] = f"/trip/{id}/?" + urllib.parse.urlencode(params)
else:
r.headers["Location"] = f"/trip/{id}/"
return r
type_to_add = args.pop("type_add", None)
if type_to_add:
newtype = TripToTripType(trip_id=str(id), trip_type_id=str(type_to_add))
db.session.add(newtype)
db.session.commit()
r = make_response("", 303)
if args:
r.headers["Location"] = f"/trip/{id}/?" + urllib.parse.urlencode(params)
else:
r.headers["Location"] = f"/trip/{id}/"
return r
type_to_remove = args.pop("type_remove", None)
if type_to_remove:
newtype = TripToTripType.query.filter_by(
trip_id=str(id), trip_type_id=str(type_to_remove)
).delete()
db.session.commit()
r = make_response("", 303)
if args:
r.headers["Location"] = f"/trip/{id}/?" + urllib.parse.urlencode(params)
else:
r.headers["Location"] = f"/trip/{id}/"
return r
trip = get_trip(id)
items = get_all_items()
categories = get_categories()
edit = args.pop("edit", None)
for item in items:
try:
db.session.add(
TripItems(trip_id=str(id), item_id=str(item.id), pick=False, pack=False)
)
db.session.commit()
except sqlalchemy.exc.IntegrityError:
db.session.rollback()
return make_response(
Base(
TripManager(
trip,
categories=categories,
active_category=None,
edit=edit,
baseurl=f"/trip/{id}/",
triptypes=get_triptypes(),
),
app.root_path,
active_page="trips",
).doc.render(),
200,
)
@app.route(
"/trip/<uuid:id>/category/<uuid:category_id>/edit/<string:attribute>/submit/",
methods=["POST"],
)
@app.route("/trip/<uuid:id>/edit/<string:attribute>/submit/", methods=["POST"])
def edit_trip_category_location_edit_submit(id, category_id=None, attribute=None):
new_value = request.form[attribute]
if attribute in ("start_date", "end_date"):
new_value = datetime.date.fromisoformat(new_value)
updates = Trip.query.filter_by(id=str(id)).update({attribute: new_value})
db.session.commit()
if updates == 0:
# todo what to do without js?
return make_response("", 404)
redirect = request.path[: -(len(f"edit/{attribute}/submit/"))]
r = make_response("", 303)
r.headers["Location"] = redirect
return r
@app.route("/trip/<uuid:id>/category/<uuid:category_id>/")
def trip_with_active_category(id, category_id):
trip = get_trip(id)
args = request.args.to_dict()
item_to_pick = args.pop("item_pick", None)
if item_to_pick:
pick_item(id, item_to_pick)
r = make_response("", 303)
if args:
r.headers[
"Location"
] = f"/trip/{id}/category/{category_id}/?" + urllib.parse.urlencode(params)
else:
r.headers["Location"] = f"/trip/{id}/category/{category_id}/"
return r
item_to_unpick = args.pop("item_unpick", None)
if item_to_unpick:
unpick_item(id, item_to_unpick)
r = make_response("", 303)
if args:
r.headers[
"Location"
] = f"/trip/{id}/category/{category_id}/?" + urllib.parse.urlencode(params)
else:
r.headers["Location"] = f"/trip/{id}/category/{category_id}/"
return r
item_to_pack = args.pop("item_pack", None)
if item_to_pack:
pack_item(id, item_to_pack)
r = make_response("", 303)
if args:
r.headers[
"Location"
] = f"/trip/{id}/category/{category_id}/?" + urllib.parse.urlencode(params)
else:
r.headers["Location"] = f"/trip/{id}/category/{category_id}/"
return r
item_to_unpack = args.pop("item_unpack", None)
if item_to_unpack:
unpack_item(id, item_to_unpack)
r = make_response("", 303)
if args:
r.headers[
"Location"
] = f"/trip/{id}/category/{category_id}/?" + urllib.parse.urlencode(params)
else:
r.headers["Location"] = f"/trip/{id}/category/{category_id}/"
return r
type_to_add = args.pop("type_add", None)
if type_to_add:
newtype = TripToTripType(trip_id=str(id), trip_type_id=str(type_to_add))
db.session.add(newtype)
db.session.commit()
r = make_response("", 303)
if args:
r.headers[
"Location"
] = f"/trip/{id}/category/{category_id}/?" + urllib.parse.urlencode(params)
else:
r.headers["Location"] = f"/trip/{id}/category/{category_id}/"
return r
type_to_remove = args.pop("type_remove", None)
if type_to_remove:
newtype = TripToTripType.query.filter_by(
trip_id=str(id), trip_type_id=str(type_to_remove)
).delete()
db.session.commit()
r = make_response("", 303)
if args:
r.headers[
"Location"
] = f"/trip/{id}/category/{category_id}/?" + urllib.parse.urlencode(params)
else:
r.headers["Location"] = f"/trip/{id}/category/{category_id}/"
return r
items = get_all_items()
categories = get_categories()
active_category = [c for c in categories if str(c.id) == str(category_id)][0]
active_category.active = True
for item in items:
try:
db.session.add(
TripItems(trip_id=str(id), item_id=str(item.id), pick=False, pack=False)
)
db.session.commit()
except sqlalchemy.exc.IntegrityError:
db.session.rollback()
edit = args.pop("edit", None)
return make_response(
Base(
TripManager(
trip,
categories=categories,
active_category=active_category,
edit=edit,
baseurl=f"/trip/{id}/category/{category_id}/",
triptypes=get_triptypes(),
),
app.root_path,
active_page="trips",
).doc.render(),
200,
)
@app.route("/inventory/category/<uuid:id>/")
def category(id):
categories = get_categories()
print(id)
active_category = [c for c in categories if str(c.id) == str(id)][0]
active_category.active = True
args = request.args.to_dict()
items = get_items(active_category)
error = False
if not is_htmx():
edit = request.args.get("edit")
if edit is not None:
match = [i for i in items if i.id == edit]
if match:
match[0].edit = True
return make_response(
Base(
InventoryItemManager(categories, items, active_category),
app.root_path,
active_page="inventory",
).doc.render(),
200,
)
def is_htmx():
return request.headers.get("HX-Request") is not None
@app.route("/inventory/item/", methods=["POST"])
def add_new_item():
name = request.form["name"]
weight = int(request.form["weight"])
category_id = request.form["category"]
add_item(name=name, weight=weight, category_id=category_id)
r = make_response("", 303)
r.headers["Location"] = f"/inventory/category/{category_id}"
return r
@app.route("/trip/", methods=["POST"])
def add_new_trip():
name = request.form["name"]
start_date = datetime.date.fromisoformat(request.form["start-date"])
end_date = datetime.date.fromisoformat(request.form["end-date"])
newid = str(uuid.uuid4())
db.session.add(Trip(id=newid, name=name, start_date=start_date, end_date=end_date, location="Unknown", temp_min=0, temp_max=0, state=TripState.Planning))
db.session.commit()
r = make_response("", 303)
r.headers["Location"] = f"/trip/{newid}" # TODO enable this
r.headers["Location"] = f"/trip/"
return r
def validate_name(name):
error, errormsg = False, None
if len(name) == 0:
error = True
errormsg = f"Name cannot be empty"
elif name.isspace():
error = True
errormsg = f"Name cannot be only whitespace"
return error, errormsg
@app.route("/inventory/item/<uuid:id>/edit/submit/", methods=["POST"])
def edit_item_submit(id):
name = request.form["name"]
weight = int(request.form["weight"])
item = InventoryItem.query.filter_by(id=str(id)).first()
if item is None:
# todo what to do without js?
return make_response("", 404)
item.name = name
item.weight = weight
db.session.commit()
r = make_response("", 303)
r.headers["Location"] = f"/inventory/category/{item.category.id}"
return r
@app.route("/inventory/item/<uuid:id>/pick/submit/", methods=["POST"])
def edit_item_pick(id):
print(request.form)
if "pick" in request.form:
pick = request.form["pick"] == "on"
else:
pick = False
print(pick)
item = InventoryItem.query.filter_by(id=str(id)).first()
if item is None:
# todo what to do without js?
return make_response("", 404)
item.picked = pick
db.session.commit()
r = make_response("", 303)
r.headers["Location"] = f"/inventory/category/{item.category.id}"
return r
@app.route("/inventory/item/<uuid:id>/pack/submit/", methods=["POST"])
def edit_item_pack(id):
print(request.form)
if "pack" in request.form:
pack = request.form["pack"] == "on"
else:
pack = False
print(pack)
item = InventoryItem.query.filter_by(id=str(id)).first()
if item is None:
# todo what to do without js?
return make_response("", 404)
item.pack = pack
db.session.commit()
r = make_response("", 303)
r.headers["Location"] = f"/inventory/category/{item.category.id}"
return r
@app.route("/inventory/item/<uuid:id>", methods=["DELETE"])
def delete_item(id):
deletions = InventoryItem.query.filter_by(id=str(id)).delete()
if deletions == 0:
return make_response("", 404)
else:
db.session.commit()
return make_response("", 200)
@app.route("/inventory/item/<uuid:id>/delete", methods=["GET"])
def delete_item_get(id):
print(request.headers)
print(request.args)
print(f"deleting {id}")
deletions = InventoryItem.query.filter_by(id=str(id)).delete()
if deletions == 0:
return make_response("", 404)
else:
db.session.commit()
r = make_response("", 303)
r.headers["Location"] = request.headers["Referer"]
return r

View File

@@ -1,16 +0,0 @@
alembic==1.8.1
click==8.1.3
dominate==2.6.0
Flask==2.1.2
Flask-Migrate==3.1.0
Flask-SQLAlchemy==2.5.1
greenlet==1.1.2
importlib-metadata==4.12.0
importlib-resources==5.9.0
itsdangerous==2.1.2
Jinja2==3.1.2
Mako==1.2.2
MarkupSafe==2.1.1
SQLAlchemy==1.4.39
Werkzeug==2.1.2
zipp==3.8.0

View File

@@ -1,12 +0,0 @@
#!/usr/bin/env bash
source ./venv/bin/activate
export FLASK_APP=packager
export FLASK_ENV=development
if (( $# == 0 )) ; then
python3 -m flask run --reload --host 0.0.0.0 --port 5000
else
python3 -m flask "${@}"
fi

View File

@@ -1 +0,0 @@
/venv/

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env bash
xtightvncviewer 127.0.0.1::5900 -passwd <(printf %s secret | vncpasswd -f)

View File

@@ -1,3 +0,0 @@
helium==3.0.8
selenium==3.141.0
urllib3==1.26.10

View File

@@ -1,10 +0,0 @@
#!/usr/bin/env bash
docker run \
--rm \
--publish 4444:4444 \
--env SE_OPTS="--session-timeout 36000" \
--shm-size="2g" \
--net=host \
--name docker-selenium \
selenium/standalone-firefox:4.3.0-20220706

Some files were not shown because too many files have changed in this diff Show More