Add optional subtitle to recipes, display under title, omit from TOC
This commit is contained in:
+26
-11
@@ -291,6 +291,13 @@ def init_db():
|
||||
if not cursor.fetchone():
|
||||
cursor.execute("ALTER TABLE recipes ADD COLUMN printed_at DATETIME NULL DEFAULT NULL AFTER image_url")
|
||||
|
||||
cursor.execute("""
|
||||
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = 'recipes' AND COLUMN_NAME = 'subtitle'
|
||||
""", (MARIADB_NAME,))
|
||||
if not cursor.fetchone():
|
||||
cursor.execute("ALTER TABLE recipes ADD COLUMN subtitle VARCHAR(255) DEFAULT '' AFTER title")
|
||||
|
||||
cursor.execute("""
|
||||
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = 'recipes' AND COLUMN_NAME = 'has_portion_table'
|
||||
@@ -375,6 +382,7 @@ def init_db():
|
||||
CREATE TABLE IF NOT EXISTS recipes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
subtitle TEXT DEFAULT '',
|
||||
category TEXT NOT NULL,
|
||||
chapter_number INTEGER NOT NULL,
|
||||
effort TEXT NOT NULL DEFAULT 'mittel',
|
||||
@@ -395,6 +403,8 @@ def init_db():
|
||||
# Migration check for SQLite
|
||||
cursor.execute("PRAGMA table_info(recipes)")
|
||||
existing_cols = [c[1] for c in cursor.fetchall()]
|
||||
if "subtitle" not in existing_cols:
|
||||
cursor.execute("ALTER TABLE recipes ADD COLUMN subtitle TEXT DEFAULT ''")
|
||||
if "source" not in existing_cols:
|
||||
cursor.execute("ALTER TABLE recipes ADD COLUMN source TEXT DEFAULT ''")
|
||||
if "printed_at" not in existing_cols:
|
||||
@@ -713,9 +723,11 @@ def add_recipe(
|
||||
source: str = "",
|
||||
image_url: str = "",
|
||||
printed_at: Optional[str] = None,
|
||||
has_portion_table: int = 0
|
||||
has_portion_table: int = 0,
|
||||
subtitle: str = ""
|
||||
) -> Dict[str, Any]:
|
||||
title = title.strip()
|
||||
subtitle = (subtitle or "").strip()
|
||||
category = category.strip()
|
||||
effort = effort.strip().lower()
|
||||
source = source.strip()
|
||||
@@ -732,10 +744,10 @@ def add_recipe(
|
||||
|
||||
res = execute_query(
|
||||
"""
|
||||
INSERT INTO recipes (title, category, chapter_number, effort, prep_time, cook_time, portions, has_portion_table, ingredients, instructions, notes, source, image_url, printed_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO recipes (title, subtitle, category, chapter_number, effort, prep_time, cook_time, portions, has_portion_table, ingredients, instructions, notes, source, image_url, printed_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(title, category, chapter_number, effort, prep_time, cook_time, portions, int(bool(has_portion_table)), ingredients, instructions, notes, source, image_url, printed_at),
|
||||
(title, subtitle, category, chapter_number, effort, prep_time, cook_time, portions, int(bool(has_portion_table)), ingredients, instructions, notes, source, image_url, printed_at),
|
||||
fetch="none",
|
||||
commit=True
|
||||
)
|
||||
@@ -758,7 +770,8 @@ def update_recipe(
|
||||
source: str = "",
|
||||
image_url: Optional[str] = None,
|
||||
printed_at: Optional[str] = None,
|
||||
has_portion_table: Optional[int] = None
|
||||
has_portion_table: Optional[int] = None,
|
||||
subtitle: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
title = title.strip()
|
||||
category = category.strip()
|
||||
@@ -772,6 +785,8 @@ def update_recipe(
|
||||
if not existing:
|
||||
raise KeyError(f"Rezept #{recipe_id} nicht gefunden.")
|
||||
|
||||
use_subtitle = (subtitle if subtitle is not None else existing.get("subtitle", "")).strip()
|
||||
|
||||
instructions = clean_instructions_text(instructions)
|
||||
|
||||
use_portion_table = int(bool(has_portion_table)) if has_portion_table is not None else existing.get("has_portion_table", 0)
|
||||
@@ -779,17 +794,17 @@ def update_recipe(
|
||||
if image_url is not None:
|
||||
query = """
|
||||
UPDATE recipes
|
||||
SET title = ?, category = ?, chapter_number = ?, effort = ?, prep_time = ?, cook_time = ?, portions = ?, has_portion_table = ?, ingredients = ?, instructions = ?, notes = ?, source = ?, image_url = ?, printed_at = ?
|
||||
SET title = ?, subtitle = ?, category = ?, chapter_number = ?, effort = ?, prep_time = ?, cook_time = ?, portions = ?, has_portion_table = ?, ingredients = ?, instructions = ?, notes = ?, source = ?, image_url = ?, printed_at = ?
|
||||
WHERE id = ?
|
||||
"""
|
||||
params = (title, category, chapter_number, effort, prep_time, cook_time, portions, use_portion_table, ingredients, instructions, notes, source, image_url, printed_at, recipe_id)
|
||||
params = (title, use_subtitle, category, chapter_number, effort, prep_time, cook_time, portions, use_portion_table, ingredients, instructions, notes, source, image_url, printed_at, recipe_id)
|
||||
else:
|
||||
query = """
|
||||
UPDATE recipes
|
||||
SET title = ?, category = ?, chapter_number = ?, effort = ?, prep_time = ?, cook_time = ?, portions = ?, has_portion_table = ?, ingredients = ?, instructions = ?, notes = ?, source = ?, printed_at = ?
|
||||
SET title = ?, subtitle = ?, category = ?, chapter_number = ?, effort = ?, prep_time = ?, cook_time = ?, portions = ?, has_portion_table = ?, ingredients = ?, instructions = ?, notes = ?, source = ?, printed_at = ?
|
||||
WHERE id = ?
|
||||
"""
|
||||
params = (title, category, chapter_number, effort, prep_time, cook_time, portions, use_portion_table, ingredients, instructions, notes, source, printed_at, recipe_id)
|
||||
params = (title, use_subtitle, category, chapter_number, effort, prep_time, cook_time, portions, use_portion_table, ingredients, instructions, notes, source, printed_at, recipe_id)
|
||||
|
||||
res = execute_query(query, params, fetch="none", commit=True)
|
||||
return get_recipe(recipe_id)
|
||||
@@ -826,9 +841,9 @@ def get_all_recipes(
|
||||
params.append(category.strip())
|
||||
|
||||
if search and search.strip():
|
||||
query += " AND (title LIKE ? OR ingredients LIKE ? OR instructions LIKE ?)"
|
||||
query += " AND (title LIKE ? OR subtitle LIKE ? OR ingredients LIKE ? OR instructions LIKE ?)"
|
||||
s = f"%{search.strip()}%"
|
||||
params.extend([s, s, s])
|
||||
params.extend([s, s, s, s])
|
||||
|
||||
if printed_status == "unprinted":
|
||||
query += " AND printed_at IS NULL"
|
||||
|
||||
@@ -580,6 +580,7 @@ def api_get_recipe(recipe_id: int):
|
||||
@app.post("/api/recipes")
|
||||
async def api_create_recipe(
|
||||
title: str = Form(...),
|
||||
subtitle: str = Form(""),
|
||||
chapter_number: int = Form(...),
|
||||
effort: str = Form("mittel"),
|
||||
prep_time: str = Form(""),
|
||||
@@ -604,6 +605,7 @@ async def api_create_recipe(
|
||||
try:
|
||||
recipe = add_recipe(
|
||||
title=title,
|
||||
subtitle=subtitle,
|
||||
category=category_title,
|
||||
chapter_number=chapter_number,
|
||||
effort=effort,
|
||||
@@ -636,6 +638,7 @@ async def api_create_recipe(
|
||||
async def api_update_recipe(
|
||||
recipe_id: int,
|
||||
title: str = Form(...),
|
||||
subtitle: Optional[str] = Form(None),
|
||||
chapter_number: int = Form(...),
|
||||
effort: str = Form("mittel"),
|
||||
prep_time: str = Form(""),
|
||||
@@ -661,6 +664,7 @@ async def api_update_recipe(
|
||||
recipe = update_recipe(
|
||||
recipe_id=recipe_id,
|
||||
title=title,
|
||||
subtitle=subtitle,
|
||||
category=category_title,
|
||||
chapter_number=chapter_number,
|
||||
effort=effort,
|
||||
|
||||
@@ -542,6 +542,14 @@
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.recipe-subtitle {
|
||||
font-family: var(--font-hand);
|
||||
font-size: 1.55rem;
|
||||
color: #4a453f;
|
||||
margin: -4px 0 12px 0;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.recipe-meta-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -945,6 +953,13 @@
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.recipe-subtitle {
|
||||
font-size: 1.25rem;
|
||||
margin-top: -4px;
|
||||
margin-bottom: 8px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.recipe-meta-row {
|
||||
font-size: 0.95rem;
|
||||
gap: 10px;
|
||||
|
||||
@@ -290,6 +290,14 @@
|
||||
color: var(--tannengruen) !important;
|
||||
}
|
||||
|
||||
.recipe-subtitle {
|
||||
font-family: var(--font-hand) !important;
|
||||
font-size: 1.05rem !important;
|
||||
line-height: 1.15 !important;
|
||||
margin: -0.5mm 0 1.5mm 0 !important;
|
||||
color: #4a453f !important;
|
||||
}
|
||||
|
||||
.recipe-meta-row {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap !important;
|
||||
@@ -685,6 +693,9 @@
|
||||
<!-- Header (Ohne Kapitelangabe & ohne Quelle im Titel) -->
|
||||
<header class="recipe-header">
|
||||
<h3 class="recipe-title">{{ recipe.title }}</h3>
|
||||
{% if recipe.subtitle and recipe.subtitle.strip() %}
|
||||
<div class="recipe-subtitle">{{ recipe.subtitle.strip() }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="recipe-meta-row">
|
||||
{% if recipe.effort %}
|
||||
@@ -1174,6 +1185,12 @@
|
||||
<input type="text" id="m-title" name="title" class="cb-input" placeholder="z. B. Klassische Waffeln" required autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Untertitel -->
|
||||
<div class="cb-form-group">
|
||||
<label for="m-subtitle" class="cb-label">Untertitel (optional)</label>
|
||||
<input type="text" id="m-subtitle" name="subtitle" class="cb-input" placeholder="z. B. mit knusprigen Croutons" autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Kapitel -->
|
||||
<div class="cb-form-group">
|
||||
<label for="m-chapter_number" class="cb-label">Kategorie / Kapitel *</label>
|
||||
|
||||
@@ -63,6 +63,9 @@
|
||||
<!-- Header (Ohne Kapitelangabe & ohne Quelle im Titel) -->
|
||||
<header class="recipe-header">
|
||||
<h2 class="recipe-title">{{ recipe.title }}</h2>
|
||||
{% if recipe.subtitle and recipe.subtitle.strip() %}
|
||||
<div class="recipe-subtitle">{{ recipe.subtitle.strip() }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="recipe-meta-row">
|
||||
{% if recipe.effort %}
|
||||
|
||||
@@ -65,6 +65,14 @@
|
||||
placeholder="z. B. Klassische Spaghetti Carbonara" required autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Untertitel -->
|
||||
<div class="cb-form-group">
|
||||
<label for="subtitle" class="cb-label">Untertitel (optional)</label>
|
||||
<input type="text" id="subtitle" name="subtitle" class="cb-input"
|
||||
value="{{ recipe.subtitle if recipe and recipe.subtitle else '' }}"
|
||||
placeholder="z. B. mit knusprigen Croutons" autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Kategorie / Kapitel -->
|
||||
<div class="cb-form-group">
|
||||
<label for="chapter_number" class="cb-label">Kategorie / Kapitel *</label>
|
||||
|
||||
Reference in New Issue
Block a user