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:
parent
66d26f5df7
commit
fad4642075
6 changed files with 115 additions and 7 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from .models import Plant, PruningLog, Location
|
from .models import Plant, PruningLog, Location, WATERING_CHOICES, SUNLIGHT_CHOICES
|
||||||
|
|
||||||
MONTH_CHOICES = [
|
MONTH_CHOICES = [
|
||||||
('1', 'Jan'), ('2', 'Feb'), ('3', 'Mar'), ('4', 'Apr'),
|
('1', 'Jan'), ('2', 'Feb'), ('3', 'Mar'), ('4', 'Apr'),
|
||||||
|
|
@ -27,10 +27,18 @@ class PlantForm(forms.ModelForm):
|
||||||
label='Or add new location',
|
label='Or add new location',
|
||||||
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'e.g. Living room'}),
|
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:
|
class Meta:
|
||||||
model = Plant
|
model = Plant
|
||||||
fields = ['name', 'location', 'is_indoor', 'bloom_months', 'notes']
|
fields = ['name', 'location', 'is_indoor', 'watering', 'sunlight', 'bloom_months', 'notes']
|
||||||
widgets = {
|
widgets = {
|
||||||
'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
||||||
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
|
|
||||||
23
plants/migrations/0008_add_watering_sunlight_to_plant.py
Normal file
23
plants/migrations/0008_add_watering_sunlight_to_plant.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
23
plants/migrations/0009_update_watering_sunlight_choices.py
Normal file
23
plants/migrations/0009_update_watering_sunlight_choices.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -36,6 +36,20 @@ class Species(models.Model):
|
||||||
return self.common_name
|
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):
|
class Plant(models.Model):
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
species = models.ForeignKey(
|
species = models.ForeignKey(
|
||||||
|
|
@ -47,6 +61,8 @@ class Plant(models.Model):
|
||||||
on_delete=models.SET_NULL, related_name='plants',
|
on_delete=models.SET_NULL, related_name='plants',
|
||||||
)
|
)
|
||||||
is_indoor = models.BooleanField(default=False)
|
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)
|
pruning_months = models.JSONField(default=list)
|
||||||
bloom_months = models.JSONField(default=list)
|
bloom_months = models.JSONField(default=list)
|
||||||
photo = models.ImageField(upload_to='plants/', blank=True)
|
photo = models.ImageField(upload_to='plants/', blank=True)
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,16 @@
|
||||||
|
|
||||||
{% if plant.location %}<p class="text-muted small mb-2">📍 {{ plant.location.name }}</p>{% endif %}
|
{% 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">
|
<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>
|
<span class="care-chip" style="background:#e3f2fd; color:#1565c0;">💧 {{ plant.species.watering }}</span>
|
||||||
{% endif %}
|
{% 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>
|
<span class="care-chip" style="background:#fff9c4; color:#7d4a00;">☀️ {{ plant.species.sunlight }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if plant.species.max_height_cm %}
|
{% if plant.species.max_height_cm %}
|
||||||
|
|
@ -43,7 +47,8 @@
|
||||||
<span class="care-chip" style="background:#e8f5e9; color:#1b5e20;">🌱 {{ plant.species.growth_rate }}</span>
|
<span class="care-chip" style="background:#e8f5e9; color:#1b5e20;">🌱 {{ plant.species.growth_rate }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if plant.species.vpc_slug %}
|
{% endif %}
|
||||||
|
{% if plant.species and plant.species.vpc_slug %}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<form method="post" action="{% url 'vpc_species_reset' plant.species.pk %}">
|
<form method="post" action="{% url 'vpc_species_reset' plant.species.pk %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
@ -55,7 +60,6 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% load plant_extras %}
|
{% load plant_extras %}
|
||||||
{% if plant.bloom_months %}
|
{% if plant.bloom_months %}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,40 @@
|
||||||
<label class="form-check-label" for="{{ form.is_indoor.id_for_label }}">Indoor plant</label>
|
<label class="form-check-label" for="{{ form.is_indoor.id_for_label }}">Indoor plant</label>
|
||||||
</div>
|
</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">
|
<div class="mb-3">
|
||||||
<label class="form-label fw-semibold">Bloom months</label>
|
<label class="form-label fw-semibold">Bloom months</label>
|
||||||
<div class="month-toggle-group">
|
<div class="month-toggle-group">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue