89 lines
2.8 KiB
Markdown
89 lines
2.8 KiB
Markdown
# 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()`:
|
|
|
|
```html
|
|
{% 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`.
|
|
|
|
### New partial: `plants/partials/card_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
|