feat: add per-plant watering and sunlight overrides with icon chips

Plant-level watering/sunlight fields override the species defaults.
Rendered as btn-check radio buttons in the edit form; detail page
shows the plant value (with emoji) falling back to species data.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Stephan Kerkman 2026-05-29 11:54:10 +02:00
parent 66d26f5df7
commit fad4642075
6 changed files with 115 additions and 7 deletions

View file

@ -1,5 +1,5 @@
from django import forms
from .models import Plant, PruningLog, Location
from .models import Plant, PruningLog, Location, WATERING_CHOICES, SUNLIGHT_CHOICES
MONTH_CHOICES = [
('1', 'Jan'), ('2', 'Feb'), ('3', 'Mar'), ('4', 'Apr'),
@ -27,10 +27,18 @@ class PlantForm(forms.ModelForm):
label='Or add new location',
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'e.g. Living room'}),
)
watering = forms.ChoiceField(
choices=[('', '')] + WATERING_CHOICES,
required=False,
)
sunlight = forms.ChoiceField(
choices=[('', '')] + SUNLIGHT_CHOICES,
required=False,
)
class Meta:
model = Plant
fields = ['name', 'location', 'is_indoor', 'bloom_months', 'notes']
fields = ['name', 'location', 'is_indoor', 'watering', 'sunlight', 'bloom_months', 'notes']
widgets = {
'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'name': forms.TextInput(attrs={'class': 'form-control'}),

View file

@ -0,0 +1,23 @@
# Generated by Django 5.2.1 on 2026-05-29 09:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('plants', '0007_plantcardphoto'),
]
operations = [
migrations.AddField(
model_name='plant',
name='sunlight',
field=models.CharField(blank=True, max_length=200),
),
migrations.AddField(
model_name='plant',
name='watering',
field=models.CharField(blank=True, max_length=100),
),
]

View file

@ -0,0 +1,23 @@
# Generated by Django 5.2.1 on 2026-05-29 09:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('plants', '0008_add_watering_sunlight_to_plant'),
]
operations = [
migrations.AlterField(
model_name='plant',
name='sunlight',
field=models.CharField(blank=True, choices=[('full_sun', '☀️ Full sun'), ('part_sun', '🌤 Part sun'), ('full_shade', '🌑 Full shade')], max_length=200),
),
migrations.AlterField(
model_name='plant',
name='watering',
field=models.CharField(blank=True, choices=[('frequent', '💧💧 Frequent'), ('regular', '💧 Regular'), ('minimum', '🌿 Minimum'), ('drought', '🌵 Drought tolerant')], max_length=100),
),
]

View file

@ -36,6 +36,20 @@ class Species(models.Model):
return self.common_name
WATERING_CHOICES = [
('frequent', '💧💧 Frequent'),
('regular', '💧 Regular'),
('minimum', '🌿 Minimum'),
('drought', '🌵 Drought tolerant'),
]
SUNLIGHT_CHOICES = [
('full_sun', '☀️ Full sun'),
('part_sun', '🌤 Part sun'),
('full_shade', '🌑 Full shade'),
]
class Plant(models.Model):
name = models.CharField(max_length=200)
species = models.ForeignKey(
@ -47,6 +61,8 @@ class Plant(models.Model):
on_delete=models.SET_NULL, related_name='plants',
)
is_indoor = models.BooleanField(default=False)
watering = models.CharField(max_length=100, blank=True, choices=WATERING_CHOICES)
sunlight = models.CharField(max_length=200, blank=True, choices=SUNLIGHT_CHOICES)
pruning_months = models.JSONField(default=list)
bloom_months = models.JSONField(default=list)
photo = models.ImageField(upload_to='plants/', blank=True)

View file

@ -28,12 +28,16 @@
{% if plant.location %}<p class="text-muted small mb-2">📍 {{ plant.location.name }}</p>{% endif %}
{% if plant.species %}
{% if plant.watering or plant.sunlight or plant.species %}
<div class="mb-3">
{% if plant.species.watering %}
{% if plant.watering %}
<span class="care-chip" style="background:#e3f2fd; color:#1565c0;">{{ plant.get_watering_display }}</span>
{% elif plant.species.watering %}
<span class="care-chip" style="background:#e3f2fd; color:#1565c0;">💧 {{ plant.species.watering }}</span>
{% endif %}
{% if plant.species.sunlight %}
{% if plant.sunlight %}
<span class="care-chip" style="background:#fff9c4; color:#7d4a00;">{{ plant.get_sunlight_display }}</span>
{% elif plant.species.sunlight %}
<span class="care-chip" style="background:#fff9c4; color:#7d4a00;">☀️ {{ plant.species.sunlight }}</span>
{% endif %}
{% if plant.species.max_height_cm %}
@ -43,7 +47,8 @@
<span class="care-chip" style="background:#e8f5e9; color:#1b5e20;">🌱 {{ plant.species.growth_rate }}</span>
{% endif %}
</div>
{% if plant.species.vpc_slug %}
{% endif %}
{% if plant.species and plant.species.vpc_slug %}
<div class="mb-3">
<form method="post" action="{% url 'vpc_species_reset' plant.species.pk %}">
{% csrf_token %}
@ -55,7 +60,6 @@
</form>
</div>
{% endif %}
{% endif %}
{% load plant_extras %}
{% if plant.bloom_months %}

View file

@ -45,6 +45,40 @@
<label class="form-check-label" for="{{ form.is_indoor.id_for_label }}">Indoor plant</label>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Watering</label>
<div class="d-flex flex-wrap gap-2">
{% for val, label in form.watering.field.choices %}
<div>
<input type="radio" class="btn-check" name="watering"
id="watering_{{ val|default:'none' }}" value="{{ val }}"
autocomplete="off"
{% if form.watering.value == val %}checked{% endif %}>
<label class="btn btn-outline-secondary btn-sm" for="watering_{{ val|default:'none' }}">{{ label }}</label>
</div>
{% endfor %}
</div>
{% if species and species.watering %}<div class="form-text">Species default: 💧 {{ species.watering }}</div>{% endif %}
{% if form.watering.errors %}<div class="text-danger small">{{ form.watering.errors }}</div>{% endif %}
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Sunlight</label>
<div class="d-flex flex-wrap gap-2">
{% for val, label in form.sunlight.field.choices %}
<div>
<input type="radio" class="btn-check" name="sunlight"
id="sunlight_{{ val|default:'none' }}" value="{{ val }}"
autocomplete="off"
{% if form.sunlight.value == val %}checked{% endif %}>
<label class="btn btn-outline-secondary btn-sm" for="sunlight_{{ val|default:'none' }}">{{ label }}</label>
</div>
{% endfor %}
</div>
{% if species and species.sunlight %}<div class="form-text">Species default: ☀️ {{ species.sunlight }}</div>{% endif %}
{% if form.sunlight.errors %}<div class="text-danger small">{{ form.sunlight.errors }}</div>{% endif %}
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Bloom months</label>
<div class="month-toggle-group">