release nouri 0.4.0 templates suggestions and mobile sheet

This commit is contained in:
2026-04-12 16:00:00 +02:00
parent b68ed62887
commit d8b56e6b67
28 changed files with 2651 additions and 492 deletions
+65 -39
View File
@@ -8,7 +8,7 @@ from flask import Flask, current_app, g
from flask.cli import with_appcontext
from werkzeug.security import generate_password_hash
from .constants import DAYPARTS
from .constants import DAYPARTS, DEFAULT_CATEGORIES
def get_db() -> sqlite3.Connection:
@@ -33,6 +33,14 @@ def table_columns(database: sqlite3.Connection, table_name: str) -> set[str]:
return {row["name"] for row in rows}
def table_exists(database: sqlite3.Connection, table_name: str) -> bool:
row = database.execute(
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?",
(table_name,),
).fetchone()
return row is not None
def add_column_if_missing(database: sqlite3.Connection, table_name: str, definition: str) -> None:
column_name = definition.split()[0]
if column_name not in table_columns(database, table_name):
@@ -50,31 +58,41 @@ def bootstrap_legacy_schema(database: sqlite3.Connection) -> None:
"""
)
existing_tables = {
row["name"]
for row in database.execute(
"SELECT name FROM sqlite_master WHERE type = 'table'"
).fetchall()
}
database.execute(
"""
CREATE TABLE IF NOT EXISTS household_categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
household_id INTEGER NOT NULL,
name TEXT NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 100,
is_active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (household_id, name)
)
"""
)
if "users" in existing_tables:
if table_exists(database, "users"):
add_column_if_missing(database, "users", "household_id INTEGER")
add_column_if_missing(database, "users", "email TEXT")
add_column_if_missing(database, "users", "role TEXT NOT NULL DEFAULT 'member'")
add_column_if_missing(database, "users", "is_active INTEGER NOT NULL DEFAULT 1")
add_column_if_missing(database, "users", "updated_at TEXT")
if "items" in existing_tables:
if table_exists(database, "items"):
add_column_if_missing(database, "items", "household_id INTEGER")
add_column_if_missing(database, "items", "owner_user_id INTEGER")
add_column_if_missing(database, "items", "target_user_id INTEGER")
add_column_if_missing(database, "items", "visibility TEXT NOT NULL DEFAULT 'shared'")
if "shopping_entries" in existing_tables:
if table_exists(database, "shopping_entries"):
add_column_if_missing(database, "shopping_entries", "household_id INTEGER")
add_column_if_missing(database, "shopping_entries", "owner_user_id INTEGER")
add_column_if_missing(database, "shopping_entries", "visibility TEXT NOT NULL DEFAULT 'shared'")
add_column_if_missing(database, "shopping_entries", "needed_for_date TEXT")
add_column_if_missing(database, "shopping_entries", "needed_for_daypart_id INTEGER")
if "plan_entries" in existing_tables:
if table_exists(database, "plan_entries"):
add_column_if_missing(database, "plan_entries", "household_id INTEGER")
add_column_if_missing(database, "plan_entries", "owner_user_id INTEGER")
add_column_if_missing(database, "plan_entries", "visibility TEXT NOT NULL DEFAULT 'shared'")
@@ -91,9 +109,11 @@ def ensure_default_household(database: sqlite3.Connection) -> int:
"INSERT INTO households (name) VALUES (?)",
("Unser Haushalt",),
)
return int(
database.execute("SELECT id FROM households ORDER BY id LIMIT 1").fetchone()["id"]
)
return int(database.execute("SELECT id FROM households ORDER BY id LIMIT 1").fetchone()["id"])
def household_ids(database: sqlite3.Connection) -> list[int]:
return [int(row["id"]) for row in database.execute("SELECT id FROM households ORDER BY id").fetchall()]
def first_user_id(database: sqlite3.Connection) -> int | None:
@@ -101,6 +121,18 @@ def first_user_id(database: sqlite3.Connection) -> int | None:
return int(row["id"]) if row else None
def sync_default_categories(database: sqlite3.Connection) -> None:
for household_id in household_ids(database):
for sort_order, name in enumerate(DEFAULT_CATEGORIES, start=10):
database.execute(
"""
INSERT OR IGNORE INTO household_categories (household_id, name, sort_order, is_active)
VALUES (?, ?, ?, 1)
""",
(household_id, name, sort_order),
)
def ensure_schema_upgrades(database: sqlite3.Connection) -> None:
add_column_if_missing(database, "users", "household_id INTEGER")
add_column_if_missing(database, "users", "email TEXT")
@@ -113,18 +145,10 @@ def ensure_schema_upgrades(database: sqlite3.Connection) -> None:
"UPDATE users SET household_id = ? WHERE household_id IS NULL",
(default_household_id,),
)
database.execute(
"UPDATE users SET role = 'member' WHERE role IS NULL OR role = ''",
)
database.execute(
"UPDATE users SET is_active = 1 WHERE is_active IS NULL",
)
database.execute(
"UPDATE users SET email = NULL WHERE TRIM(COALESCE(email, '')) = ''",
)
database.execute(
"UPDATE users SET updated_at = COALESCE(updated_at, created_at, CURRENT_TIMESTAMP)"
)
database.execute("UPDATE users SET role = 'member' WHERE role IS NULL OR role = ''")
database.execute("UPDATE users SET is_active = 1 WHERE is_active IS NULL")
database.execute("UPDATE users SET email = NULL WHERE TRIM(COALESCE(email, '')) = ''")
database.execute("UPDATE users SET updated_at = COALESCE(updated_at, created_at, CURRENT_TIMESTAMP)")
admin_row = database.execute(
"SELECT id FROM users WHERE role = 'admin' AND is_active = 1 ORDER BY id LIMIT 1"
@@ -132,16 +156,16 @@ def ensure_schema_upgrades(database: sqlite3.Connection) -> None:
if admin_row is None:
first_id = first_user_id(database)
if first_id is not None:
database.execute(
"UPDATE users SET role = 'admin' WHERE id = ?",
(first_id,),
)
database.execute("UPDATE users SET role = 'admin' WHERE id = ?", (first_id,))
default_owner_id = first_user_id(database)
for table_name in ("items", "shopping_entries", "plan_entries"):
add_column_if_missing(database, table_name, "household_id INTEGER")
add_column_if_missing(database, table_name, "owner_user_id INTEGER")
add_column_if_missing(database, table_name, "visibility TEXT NOT NULL DEFAULT 'shared'")
add_column_if_missing(database, "items", "target_user_id INTEGER")
add_column_if_missing(database, "shopping_entries", "needed_for_date TEXT")
add_column_if_missing(database, "shopping_entries", "needed_for_daypart_id INTEGER")
if default_owner_id is not None:
database.execute(
@@ -175,15 +199,11 @@ def ensure_schema_upgrades(database: sqlite3.Connection) -> None:
(default_household_id, default_owner_id),
)
else:
database.execute(
"UPDATE items SET visibility = 'shared' WHERE visibility IS NULL OR visibility = ''"
)
database.execute(
"UPDATE shopping_entries SET visibility = 'shared' WHERE visibility IS NULL OR visibility = ''"
)
database.execute(
"UPDATE plan_entries SET visibility = 'shared' WHERE visibility IS NULL OR visibility = ''"
)
database.execute("UPDATE items SET visibility = 'shared' WHERE visibility IS NULL OR visibility = ''")
database.execute("UPDATE shopping_entries SET visibility = 'shared' WHERE visibility IS NULL OR visibility = ''")
database.execute("UPDATE plan_entries SET visibility = 'shared' WHERE visibility IS NULL OR visibility = ''")
sync_default_categories(database)
database.execute(
"""
@@ -198,6 +218,12 @@ def ensure_schema_upgrades(database: sqlite3.Connection) -> None:
ON items (household_id, visibility, availability_state)
"""
)
database.execute(
"""
CREATE INDEX IF NOT EXISTS idx_items_target_user
ON items (target_user_id)
"""
)
database.execute(
"""
CREATE INDEX IF NOT EXISTS idx_plan_entries_household_visibility