Files
Kochbuch/templates/recipes/list.html
T
Kyle DammannandClaude Opus 5.5 6f7e4f92ca Add phase 2: recipes with visibility, photos, search and favourites
- Recipe model with ingredients (amount/range, unit, group, note), steps,
  lowercase tags, author notes, source URL and private/public visibility
- Central RecipeQuerySet.visible_to() used by every read path; edit only
  by owner, delete by owner or admin (visible recipes only)
- Text-based editing: ingredient/step parser with roundtrip and scaling
- Photo upload (JPEG/PNG/WebP/HEIC), re-encoded to WebP without EXIF,
  thumbnails, cover image, served only through permission-checked view
- Search across title, description, ingredients, tags; ingredient and tag
  filters; "mine" and favourites views
- Admin can delete all recipes of a deactivated user
- Acceptance tests: private recipes unreachable for others and admins
  in list, search, tags, favourites, detail, edit, delete and image URLs

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:37:13 +01:00

74 lines
2.7 KiB
HTML

{% extends "base.html" %}
{% block title %}Rezepte{% endblock %}
{% block content %}
<div class="page-head">
<h1>Rezepte</h1>
<a class="button" href="{% url 'recipes:create' %}">+ Neu</a>
</div>
<form method="get" class="search" role="search">
<div class="search-row">
{{ form.q }}
<button type="submit" class="button">Suchen</button>
</div>
<details class="filters"{% if form.zutat.value or form.ansicht.value %} open{% endif %}>
<summary>Filter</summary>
<div class="field"><label for="{{ form.zutat.id_for_label }}">{{ form.zutat.label }}</label>{{ form.zutat }}</div>
<div class="field"><label for="{{ form.ansicht.id_for_label }}">{{ form.ansicht.label }}</label>{{ form.ansicht }}</div>
</details>
{% if active_tag %}<input type="hidden" name="tag" value="{{ active_tag }}">{% endif %}
</form>
{% if tags %}
<nav class="chips" aria-label="Tags">
{% for tag in tags %}
{% if tag.name == active_tag %}
<a class="chip active" href="{% querystring tag=None seite=None %}">#{{ tag.name }} ✕</a>
{% else %}
<a class="chip" href="{% querystring tag=tag.name seite=None %}">#{{ tag.name }}</a>
{% endif %}
{% endfor %}
</nav>
{% endif %}
{% if page.object_list %}
<ul class="recipe-grid">
{% for recipe in page.object_list %}
<li>
<a class="recipe-card" href="{% url 'recipes:detail' recipe.pk %}">
{% with cover=recipe.cover %}
{% if cover %}
<img src="{% url 'recipes:photo' cover.uuid 'klein' %}" alt="" loading="lazy" width="480" height="360">
{% else %}
<div class="placeholder" aria-hidden="true">🍲</div>
{% endif %}
{% endwith %}
<div class="recipe-card-body">
<strong>{{ recipe.title }}</strong>
<span class="muted">
{% if recipe.total_minutes %}{{ recipe.total_minutes }} Min. · {% endif %}{{ recipe.owner.display_name }}
{% if not recipe.is_public %} · 🔒{% endif %}{% if recipe.is_favorite %} · ★{% endif %}
</span>
</div>
</a>
</li>
{% endfor %}
</ul>
{% if page.has_other_pages %}
<nav class="pager">
{% if page.has_previous %}<a class="button secondary" href="{% querystring seite=page.previous_page_number %}">← Zurück</a>{% endif %}
<span class="muted">Seite {{ page.number }} von {{ page.paginator.num_pages }}</span>
{% if page.has_next %}<a class="button secondary" href="{% querystring seite=page.next_page_number %}">Weiter →</a>{% endif %}
</nav>
{% endif %}
{% elif filtered %}
<p class="muted">Keine Rezepte gefunden. <a href="{% url 'recipes:list' %}">Filter zurücksetzen</a></p>
{% else %}
<div class="card empty">
<p>Noch keine Rezepte. Leg das erste an!</p>
<a class="button" href="{% url 'recipes:create' %}">Rezept anlegen</a>
</div>
{% endif %}
{% endblock %}