Start with a proper setup

This commit is contained in:
2022-09-16 15:54:46 +02:00
parent 16e2f65c84
commit 7857c78bbe
30 changed files with 2344 additions and 685 deletions

View File

@@ -1,3 +1,6 @@
__pycache__/
/venv/
*.sqlite
*.sqlite3
*.bak
*.bundle

42
python_flask/NOTES.md Normal file
View File

@@ -0,0 +1,42 @@
# 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

Binary file not shown.

View File

@@ -2,6 +2,7 @@ import uuid
import sqlalchemy
import csv
from flask import Flask
from flask_migrate import Migrate
from .helpers import *
@@ -10,56 +11,11 @@ from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{app.root_path}/../db.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()
try:
categories = (
{"id": uuid.uuid4(), "name": "Sleeping"},
{"id": uuid.uuid4(), "name": "Shelter"},
{"id": uuid.uuid4(), "name": "Fire"},
{"id": uuid.uuid4(), "name": "Cooking"},
{"id": uuid.uuid4(), "name": "Water"},
{"id": uuid.uuid4(), "name": "Protection"},
{"id": uuid.uuid4(), "name": "Tools"},
{"id": uuid.uuid4(), "name": "Insulation"},
{"id": uuid.uuid4(), "name": "Electronics"},
{"id": uuid.uuid4(), "name": "Carry"},
{"id": uuid.uuid4(), "name": "Medic"},
{"id": uuid.uuid4(), "name": "Hygiene"},
)
for category in categories:
db.session.add(
PackageListItemCategory(
id=str(category['id']),
name=category['name'],
description="",
)
)
with open("./items.csv") as csvfile:
reader = csv.reader(csvfile, delimiter=',')
for row in reader:
print(row)
(name, category, weight) = row
db.session.add(
PackageListItem(
id=str(uuid.uuid4()),
name=name,
description="",
weight=weight,
category_id=str([c['id'] for c in categories if c['name'] == category][0])
)
)
print("db init done")
db.session.commit()
except sqlalchemy.exc.IntegrityError:
pass

View File

@@ -0,0 +1,55 @@
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,35 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
def CategoryList(categories):
with t.div(id="packagelist-table") as doc:
t.h1("Categories", _class=cls("text-2xl", "mb-5"))
with t.table(
id="packagelist-table",
_class=cls(
"table",
"table-auto",
"border-collapse",
"border-spacing-0",
"border",
"w-full",
),
):
with t.tbody() as b:
for category in categories:
with t.tr(_class=cls("h-10", "hover:bg-purple-200")) as doc:
with t.td(_class=cls("border", "p-0", "m-0")):
t.a(
category.name,
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("block", "p-2", "m-2"),
)
return doc

View File

@@ -4,19 +4,15 @@ import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
class Home:
def __init__(self, element, root_path):
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()))
doc.add(element.doc)
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

@@ -0,0 +1,85 @@
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

@@ -0,0 +1,143 @@
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

@@ -0,0 +1,194 @@
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

@@ -0,0 +1,33 @@
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

@@ -0,0 +1,152 @@
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,33 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
def ItemList(items):
with t.div(id="packagelist-table") as doc:
t.h1("Items", _class=cls("text-2xl", "mb-5"))
with t.table(
id="packagelist-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:
for item in items:
with t.tr(_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-200")) as doc:
t.td(item.name, _class=cls("border", "px-2")),
t.td(str(item.weight), _class=cls("border", "px-2")),
return doc

View File

@@ -1,120 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
def NewPackageList(name=None, description=None, error=False, errormsg=None):
assert not (error and not errormsg)
with t.form(
id="new-pkglist",
name="new_pkglist",
data_hx_post="/list/",
data_hx_target="#pkglist-manager",
data_hx_swap="outerHTML",
action="/list/",
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 package list", _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="listname",
_class=cls("font-bold", "w-1/2", "p-2", "text-center"),
)
with t.span(_class=cls("w-1/2")):
t._input(
type="text",
id="listname",
name="name",
**{"value": name} if name is not None else {},
data_hx_target="#new-pkglist",
data_hx_post="/list/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(
"Description",
_for="listdesc",
_class=cls("font-bold", "w-1/2", "text-center"),
)
with t.span(_class=cls("w-1/2")):
t._input(
type="text",
id="listdesc",
name="description",
**{"value": description} if description 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",
),
)
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

@@ -0,0 +1,145 @@
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,21 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from . import CategoryList, ItemList
from ..helpers import *
class PackageListItemManager:
def __init__(
self, categories, items, 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-1")):
CategoryList(categories),
with t.div(_class=cls("col-span-3")):
ItemList(items),
self.doc = doc

View File

@@ -1,21 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from . import NewPackageList, PackageListTable
from ..helpers import *
class PackageListManager:
def __init__(
self, pkglists, name=None, description=None, error=False, errormsg=None
):
assert not (error and not errormsg)
with t.div(id="pkglist-manager", _class=cls("p-8", "max-w-xl")) as doc:
PackageListTable(pkglists),
NewPackageList(
name=name, description=description, error=error, errormsg=errormsg
)
self.doc = doc

View File

@@ -1,197 +0,0 @@
import dominate
import dominate.tags as t
from dominate.util import raw
from ..helpers import *
class PackageListTableRowEdit:
def __init__(self, pkglist):
error, errormsg = pkglist.error, pkglist.errormsg
assert not (error and not errormsg)
with t.tr(
_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-200"),
id="pkglist-edit-row",
**{
"x-data": '{ edit_submit_enabled: document.getElementById("listedit-name").value.trim().length !== 0 }'
},
) as doc:
with t.td(colspan=3, _class=cls("border-none", "bg-purple-100", "h-10")):
with t.div():
t.form(
id="edit-pkglist",
action=f"/list/{pkglist.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",
)
if error:
t.p(errormsg, _class=cls("text-red-400", "text-sm"))
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="listedit-name",
form="edit-pkglist",
name="name",
value=pkglist.name if error else pkglist.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",
name="description",
form="edit-pkglist",
value=pkglist.description,
)
with t.td(
_class=cls(
"border",
"bg-red-200",
"hover:bg-red-400",
"cursor-pointer",
"w-8",
"text-center",
),
id="edit-packagelist-abort",
):
with t.a(
href="/",
data_hx_post=f"/list/{pkglist.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-packagelist-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-pkglist"):
t.span(_class=cls("mdi", "mdi-content-save", "text-xl")),
self.doc = doc
class PackageListTableRowNormal:
def __init__(self, pkglist):
with t.tr(_class=cls("h-10", "even:bg-gray-100", "hover:bg-purple-200")) as doc:
t.td(pkglist.name, _class=cls("border", "px-2")),
t.td(str(pkglist.description), _class=cls("border", "px-2")),
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"/list/{pkglist.id}",
data_hx_target="closest tr",
data_hx_swap="outerHTML",
href=f"/list/{pkglist.id}/delete",
):
with t.button():
t.span(_class=cls("mdi", "mdi-delete", "text-xl"))
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"/list/{pkglist.id}/edit",
href=f"/?edit={pkglist.id}",
data_hx_target="closest tr",
data_hx_swap="outerHTML",
id="edit-packagelist",
):
with t.button():
t.span(_class=cls("mdi", "mdi-pencil", "text-xl")),
with t.td(
_class=cls(
"border",
"bg-green-200",
"hover:bg-green-400",
"cursor-pointer",
"w-8",
"text-center",
)
):
with t.button():
t.span(_class=cls("mdi", "mdi-arrow-right", "text-xl"))
# data_hx_post=f"/list/{pkglist.id}/show",
self.doc = doc
class PackageListTableRow:
def __init__(self, pkglist):
if pkglist.edit:
self.doc = PackageListTableRowEdit(pkglist).doc
else:
self.doc = PackageListTableRowNormal(pkglist).doc
def PackageListTable(pkglists):
with t.div(id="packagelist-table") as doc:
t.h1("Package Lists", _class=cls("text-2xl", "mb-5"))
with t.table(
id="packagelist-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("Description", _class=cls("border", "p-2")),
t.th(_class=cls("border p-2")),
t.th(_class=cls("border p-2")),
t.th(_class=cls("border p-2")),
_class="h-10",
)
with t.tbody() as b:
for pkglist in pkglists:
PackageListTableRow(pkglist).doc
return doc

View File

@@ -0,0 +1,134 @@
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

@@ -0,0 +1,266 @@
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

@@ -0,0 +1,21 @@
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

@@ -0,0 +1,19 @@
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

@@ -0,0 +1,381 @@
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

@@ -0,0 +1,96 @@
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,13 +1,6 @@
from .NewPackageList import NewPackageList
from .PackageListTable import (
PackageListTable,
PackageListTableRowEdit,
PackageListTableRowNormal,
PackageListTableRow,
)
from .ItemList import ItemList
from .CategoryList import CategoryList
from .PackageListManager import PackageListManager
from .PackageListItemManager import PackageListItemManager
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,36 +1,86 @@
from . import db
import enum
class PackageList(db.Model):
__tablename__ = "packagelist"
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("PackageListItem", backref="packagelist", lazy=True)
items = db.relationship("InventoryItem", backref="category", lazy=True)
edit = False
error = False
errormsg = None
active = False
class PackageListItemCategory(db.Model):
__tablename__ = "packagelistitemcategory"
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)
items = db.relationship("PackageListItem", backref="category", lazy=True)
class PackageListItem(db.Model):
__tablename__ = "packagelistitem"
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)
# packagelist_id = db.Column(
# db.String(36), db.ForeignKey("packagelist.id"), nullable=False
# )
weight = db.Column(db.Integer, nullable=False)
category_id = db.Column(
db.String(36), db.ForeignKey("packagelistitemcategory.id"), nullable=False
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

@@ -5,121 +5,435 @@ 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 (
PackageListManager,
PackageListItemManager,
NewPackageList,
InventoryItemManager,
Base,
Home,
PackageListTableRowEdit,
PackageListTableRowNormal,
PackageListTableRow,
TripList,
TripManager,
InventoryItemDetails,
)
from flask import request, make_response
def get_packagelists():
return PackageList.query.all()
def get_categories():
return PackageListItemCategory.query.all()
return InventoryItemCategory.query.all()
def get_trips():
return Trip.query.all()
def get_all_items():
return PackageListItem.query.all()
return InventoryItem.query.all()
def get_triptypes():
return TripType.query.all()
def get_items(category):
return PackageListItem.query.filter_by(category_id=str(category.id))
def get_packagelist_by_id(id):
return PackageList.query.filter_by(id=str(id)).first()
return InventoryItem.query.filter_by(category_id=str(category.id))
def add_packagelist(name, description):
try:
db.session.add(
PackageList(id=str(uuid.uuid4()), name=name, description=description)
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()
except sqlalchemy.exc.IntegrityError:
db.session.rollback()
return False
def delete_packagelist(id):
deletions = PackageList.query.filter_by(id=str(id)).delete()
if deletions == 0:
return False
else:
db.session.commit()
return True
)
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 = [p for p in packagelists if p.id == edit]
match = [i for i in items if i.id == edit]
if match:
match[0].edit = True
error = request.args.get("error")
if error and bool(int(error)):
match[0].error = True
errormsg = request.args.get("msg")
if errormsg:
match[0].errormsg = errormsg
else:
name = request.args.get("name")
if name:
match[0].errormsg = f"Invalid name: {name}"
else:
match[0].errormsg = f"Invalid name"
return make_response(
Home(PackageListItemManager(categories, items), app.root_path).doc.render(), 200
Base(
InventoryItemManager(categories, items, active_category=None),
app.root_path,
active_page="inventory",
).doc.render(),
200,
)
@app.route("/category/<uuid:id>")
@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)
for c in categories:
print(f"{c.id} | {c.name}")
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 = [p for p in packagelists if p.id == edit]
match = [i for i in items if i.id == edit]
if match:
match[0].edit = True
error = request.args.get("error")
if error and bool(int(error)):
match[0].error = True
errormsg = request.args.get("msg")
if errormsg:
match[0].errormsg = errormsg
else:
name = request.args.get("name")
if name:
match[0].errormsg = f"Invalid name: {name}"
else:
match[0].errormsg = f"Invalid name"
return make_response(
Home(PackageListItemManager(categories, items), app.root_path).doc.render(), 200
Base(
InventoryItemManager(categories, items, active_category),
app.root_path,
active_page="inventory",
).doc.render(),
200,
)
@@ -127,33 +441,33 @@ def is_htmx():
return request.headers.get("HX-Request") is not None
@app.route("/list/", methods=["POST"])
def add_new_list():
@app.route("/inventory/item/", methods=["POST"])
def add_new_item():
name = request.form["name"]
description = request.form["description"]
weight = int(request.form["weight"])
category_id = request.form["category"]
error, errormsg = validate_name(name)
add_item(name=name, weight=weight, category_id=category_id)
if not error:
if add_packagelist(name=name, description=description) is False:
error = True
errormsg = f'Name "{name}" already exists'
r = make_response("", 303)
r.headers["Location"] = f"/inventory/category/{category_id}"
return r
if is_htmx():
return make_response(
PackageListManager(
get_packagelists(),
name=name,
description=description,
error=error,
errormsg=errormsg,
).doc.render(),
200 if error else 201,
)
else:
r = make_response("", 303)
r.headers["Location"] = "/"
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))
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):
@@ -169,93 +483,92 @@ def validate_name(name):
return error, errormsg
@app.route("/list/name/validate", methods=["POST"])
def validate_list_name():
@app.route("/inventory/item/<uuid:id>/edit/submit/", methods=["POST"])
def edit_item_submit(id):
name = request.form["name"]
weight = int(request.form["weight"])
error, errormsg = validate_name(name)
if not error:
if PackageList.query.filter_by(name=name).first() is not None:
error = True
errormsg = f'Name "{name}" already exists'
doc = NewPackageList(name=name, error=error, errormsg=errormsg)
return make_response(doc.render(), 200)
@app.route("/list/<uuid:id>/edit/cancel", methods=["POST"])
def edit_list_cancel(id):
print("cancelling" * 20)
pkglist = PackageList.query.filter_by(id=str(id)).first()
return make_response(PackageListTableRowNormal(pkglist).doc.render(), 200)
@app.route("/list/<uuid:id>/edit/submit/", methods=["POST"])
def edit_list_submit(id):
name = request.form["name"]
description = request.form["description"]
error, errormsg = validate_name(name)
if error:
if is_htmx():
return make_response(
PackageListTableRowEdit(error=True, errormsg=errormsg).doc.render(), 200
)
else:
r = make_response("", 303)
r.headers["Location"] = f"/?edit={id}&error=1&msg={errormsg}"
return r
pkglist = PackageList.query.filter_by(id=str(id)).first()
if pkglist is None:
item = InventoryItem.query.filter_by(id=str(id)).first()
if item is None:
# todo what to do without js?
return make_response("", 404)
pkglist.name = name
pkglist.description = description
item.name = name
item.weight = weight
try:
db.session.commit()
except sqlalchemy.exc.IntegrityError:
db.session.rollback()
errormsg = f'Name "{name}" already exists'
if is_htmx():
pkglist.error = True
pkglist.errormsg = errormsg
return make_response(PackageListTableRowEdit(pkglist).doc.render(), 200)
else:
r = make_response("", 303)
r.headers["Location"] = f"/?edit={id}&name={name}&error=1&msg={errormsg}"
return r
db.session.commit()
if is_htmx():
return make_response(PackageListTableRowNormal(pkglist).doc.render(), 200)
else:
r = make_response("", 303)
r.headers["Location"] = "/"
return r
@app.route("/list/<uuid:id>/edit", methods=["POST"])
def edit_list(id):
pkglist = get_packagelist_by_id(id)
out = PackageListTableRowEdit(pkglist).doc
return make_response(out.render(), 200)
@app.route("/list/<uuid:id>/delete", methods=["GET"])
def delete_list_via_get(id):
if not delete_packagelist(id=id):
return make_response("", 404)
r = make_response("", 303)
r.headers["Location"] = "/"
r.headers["Location"] = f"/inventory/category/{item.category.id}"
return r
@app.route("/list/<uuid:id>", methods=["DELETE"])
def delete_list(id):
if not delete_packagelist(id=id):
@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,11 +1,15 @@
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

View File

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