bloombase/docs/superpowers/specs/2026-05-28-plant-card-photos-design.md
Stephan Kerkman 90c209aef0 docs: add plant card photos design spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 21:23:32 +02:00

2.8 KiB

Plant Card Photos — Design Spec

Date: 2026-05-28

Overview

Allow users to store photos of the physical plant label/care card that came with a plant. Up to 3 photos per plant (e.g. front and back of the card). Accessed via a small button on the plant detail page, opening a dedicated card page — keeping the detail page clean.


Model

New model PlantCardPhoto:

  • plant — ForeignKey to Plant, on_delete=CASCADE, related_name='card_photos'
  • image — ImageField, upload_to='plants/cards/'
  • uploaded_at — DateTimeField, auto_now_add=True

No thumbnail concept. Ordering by uploaded_at.

The upload view enforces a maximum of 3 card photos per plant (silently blocks if already at 3).


Views & URLs

Two new views in plants/views/plants.py, both @require_POST:

View URL Action
upload_card_photo(request, pk) plants/<pk>/card-photos/upload/ Creates a PlantCardPhoto, re-renders the card page partial
delete_card_photo(request, card_photo_pk) card-photos/<pk>/delete/ Deletes the file and record, re-renders the card page partial

A new GET view:

View URL
plant_card(request, pk) plants/<pk>/card/

The card page renders plants/plant_card.html with all card photos for the plant.


Templates

plants/views/plants.py change

The plant_detail view's prefetch_related call needs 'card_photos' added so the card_photos.exists() check in the template doesn't fire an extra query.

plants/plant_detail.html change

Add a small button near the bottom (above the delete link), rendered only when plant.card_photos.exists():

{% if plant.card_photos.exists %}
<a href="{% url 'plant_card' plant.pk %}" class="btn btn-sm btn-outline-secondary mb-2">🪧 View plant card</a>
{% endif %}

New: plants/plant_card.html

Full page extending base.html:

  • Back button → plant_detail
  • Title: "Plant card"
  • Photo grid (portrait thumbnails, 3-column) with delete button per photo
  • Upload form with capture="environment" for mobile camera
  • Upload form hidden when 3 photos already uploaded
  • Hint text: "Up to 3 photos — front & back of the label"

The upload and delete forms use htmx (hx-post, hx-target="#card-gallery", hx-swap="outerHTML") targeting a <div id="card-gallery"> that wraps the grid + upload form, following the same pattern as photo_gallery.html.

Contains the photo grid and upload form, rendered by both the initial page load and htmx responses.


Migration

Single migration adding PlantCardPhoto.


Out of Scope

  • OCR or data extraction from card photos (future work)
  • Viewing card photos inline on the plant detail page
  • Lightbox / full-screen image viewer