# PlantDB — Design Spec **Date:** 2026-05-27 **Stack:** Django 5 + Bootstrap 5 + HTMX **Deployment:** Docker Compose on homelab --- ## 1. Overview PlantDB is a mobile-first family plant and flower registry. It tracks both indoor and outdoor plants, stores care information per species, surfaces a pruning calendar, and integrates with the Perenual API to auto-populate species data. No user accounts — a single shared instance for the whole family. --- ## 2. Architecture ``` Browser (mobile-first) │ Bootstrap 5 + HTMX (partial page updates) ▼ Nginx (reverse proxy + static/media files) ▼ Gunicorn + Django 5 ▼ SQLite (single file, Docker volume) │ └── Perenual API (external, cached on first lookup) ``` **Key principles:** - Server-rendered Django templates throughout; HTMX handles dynamic interactions (species search live results, pruning log updates) without a separate API layer. - Perenual API is called only once per species — results are cached in the `Species` table. No recurring API cost after initial lookups. - All uploaded media (personal plant photos) stored in a Docker volume and served by Nginx. --- ## 3. Data Model ### `Species` Populated from Perenual API or entered manually. One row per plant species. | Field | Type | Notes | |---|---|---| | `id` | PK | | | `common_name` | CharField | | | `scientific_name` | CharField | | | `description` | TextField | | | `watering` | CharField | e.g. "Weekly", "Twice a week" | | `sunlight` | CharField | e.g. "Full sun", "Partial shade" | | `shadow_tolerance` | CharField | e.g. "Low", "Medium", "High" | | `max_height_cm` | IntegerField | nullable | | `growth_rate` | CharField | e.g. "Slow", "Moderate", "Fast" | | `growth_season` | CharField | e.g. "Spring–Summer" | | `pruning_months` | JSONField | Default schedule, e.g. `[2, 6, 8]` — pre-fills the Plant field | | `api_image_url` | URLField | Image from Perenual, nullable | | `perenual_id` | IntegerField | Unique, nullable — prevents duplicate fetches | ### `Plant` One row per physical plant owned by the family. | Field | Type | Notes | |---|---|---| | `id` | PK | | | `name` | CharField | User's nickname, e.g. "Big rose on back fence" | | `species` | FK → Species | Nullable — plant can exist without a species link | | `location` | CharField | Free text, e.g. "Living room windowsill" | | `is_indoor` | BooleanField | | | `pruning_months` | JSONField | This plant's actual pruning schedule — pre-filled from species, editable per plant | | `photo` | ImageField | Personal photo, stored in media volume, nullable | | `notes` | TextField | Free text, nullable | | `date_added` | DateField | Auto-set on creation | ### `PruningLog` One row per pruning event, logged by the family. | Field | Type | Notes | |---|---|---| | `id` | PK | | | `plant` | FK → Plant | | | `pruned_on` | DateField | | | `notes` | TextField | nullable | --- ## 4. Pages & Views ### Dashboard (home `/`) - Summary tiles: total plant count, number of plants due for pruning this month. - Upcoming pruning list: overdue plants first (red), then due this month (amber). - Buttons: "View all plants", "Add plant". - HTMX: none needed — static server render on load. ### Plant List (`/plants/`) - Scrollable list of all plants, each showing name, location, indoor/outdoor badge, and thumbnail. - Search bar at top — HTMX live search filters the list on each keystroke (targets the list partial). - Filter chips: All / Indoor / Outdoor. - FAB-style "+" button links to Add Plant. ### Plant Detail (`/plants//`) - Full-width personal photo hero (falls back to API species image if no personal photo). - Name, scientific name (italic), indoor/outdoor badge. - Location line. - Care chips: 💧 watering · ☀️ sunlight · 📏 max height. - Species reference image (small, below care info) if API image available and personal photo also exists. - Pruning strip: recommended months + last pruned date. Amber highlight if due this month, red if overdue. - "Log pruning" button → inline HTMX form (date + optional notes), updates pruning strip on success. - Pruning history accordion: list of all `PruningLog` entries for this plant. - Edit and Delete actions. ### Add Plant (`/plants/add/`) Two-step flow: **Step 1 — Species search (optional)** - Search input with HTMX live lookup against Perenual API (debounced, 300ms). - Results show common name, scientific name, thumbnail. - Selecting a result caches the species in the DB (if not already cached) and pre-fills the form. - "Skip — enter manually" link bypasses to step 2 with a blank form. **Step 2 — Plant form** - Nickname field (required). - Location field (free text). - Indoor/Outdoor toggle. - Care fields (watering, sunlight, etc.) — pre-filled from species if selected, all editable. - Pruning months — pre-filled from species, shown as a 12-checkbox month picker. - Photo section: "Take photo" (camera capture via ``). The species API image, if available, is always shown automatically on the plant detail as a reference — no separate action needed. - Notes textarea. - Save button. ### Edit Plant (`/plants//edit/`) Same form as Add, pre-populated. ### Pruning Calendar (`/pruning/`) - Grouped by urgency: 1. **Overdue** (red) — plants with a recommended pruning month in the past and no log entry since. 2. **This month** (amber) — plants with current month in `pruning_months`. 3. **Next month** (grey) — plants due next month. 4. **Later this year** — collapsed section, expandable. - Each entry: plant name, location, recommended months, last pruned date. Tap to open plant detail. - Month strip at top (scrollable) to jump to any month and see what's scheduled. ### Species Detail (`/species//`) — optional read-only - Shows all species info as reference. Linked from plant detail. Not editable directly (edit via the plant form). --- ## 5. Perenual API Integration - **Endpoint used:** `GET https://perenual.com/api/species-list?q=&key=` - **API key:** stored in Django settings via environment variable `PERENUAL_API_KEY`. - **Cache strategy:** on first successful lookup, a `Species` row is created with `perenual_id` set. Any subsequent search that returns a species already in the DB skips the API entirely. - **Free tier limit:** 100 requests/day. Given that lookups only happen when adding new plants (not on every page view), this is not a practical constraint for a family app. - **Fallback:** if the API is unreachable or returns an error, the add-plant form stays open with an empty species form for manual entry. No hard dependency on the API. --- ## 6. Pruning Logic A plant is considered **overdue** for pruning if: - It has at least one `pruning_month` that fell within the past 12 months, AND - There is no `PruningLog` entry with `pruned_on` falling within that month's window. A plant is **due this month** if the current month is in `Plant.pruning_months` and not yet logged this month. Using a 12-month rolling window (rather than calendar-year) correctly handles plants due in December when checked in January. The pruning calendar view computes this in a Django queryset annotated with the current date — no background tasks or scheduled jobs needed. --- ## 7. Deployment ### Docker Compose ``` services: web: Django + Gunicorn nginx: Reverse proxy + static/media serving volumes: db: SQLite file media: Uploaded plant photos ``` - Accessible on the local network via homelab IP or a local hostname (e.g. `plantdb.home`). - No SSL required for LAN use. - `docker compose up -d` starts everything; `docker compose down` stops it. - Database backup: copy the SQLite volume file. Simple enough to add to an existing homelab backup routine. ### Environment Variables | Variable | Purpose | |---|---| | `PERENUAL_API_KEY` | Perenual API key | | `SECRET_KEY` | Django secret key | | `DEBUG` | `False` in production | | `ALLOWED_HOSTS` | Homelab hostname/IP | --- ## 8. Static & Media Files - Static files (CSS, JS, Bootstrap): collected via `manage.py collectstatic`, served by Nginx. - Media files (plant photos): stored in `/media/` inside the container, mounted as a Docker volume, served by Nginx under `/media/`. --- ## 9. Dependencies | Package | Purpose | |---|---| | `django` | Web framework | | `gunicorn` | WSGI server | | `django-htmx` | HTMX integration helpers | | `pillow` | Image handling for `ImageField` | | `requests` | Perenual API calls | No frontend build step — Bootstrap 5 and HTMX loaded via CDN or bundled as static files. --- ## 10. Out of Scope - User authentication / accounts - Watering or feeding reminders - Push notifications - Multi-language support - Public internet access / SSL (LAN only) - Offline / PWA mode