bloombase/plants/forms.py
Stephan Kerkman b88ba03884 feat: location FK, multi-photo gallery, hide pruning history
- Location model: plants now FK to Location instead of free-text CharField
- Data migration 0004 converts existing location strings to Location objects
- PlantForm uses ModelChoiceField dropdown + inline "add new location" text field
- PlantPhoto model: multiple photos per plant with is_thumbnail flag
- Plant.thumbnail_url property: picks marked thumbnail, falls back to first photo then legacy photo field
- Photo gallery partial: upload, set thumbnail (★), delete (✕) with HTMX swaps on #photo-gallery
- plant_detail: uses thumbnail_url for hero image, pruning history section removed
- plant_list search updated: location__name__icontains instead of location__icontains
- Three new URL routes: upload_photo, set_thumbnail, delete_photo
- All 53 tests updated and passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 18:27:45 +02:00

65 lines
2.3 KiB
Python

from django import forms
from .models import Plant, PruningLog, Location
MONTH_CHOICES = [
('1', 'Jan'), ('2', 'Feb'), ('3', 'Mar'), ('4', 'Apr'),
('5', 'May'), ('6', 'Jun'), ('7', 'Jul'), ('8', 'Aug'),
('9', 'Sep'), ('10', 'Oct'), ('11', 'Nov'), ('12', 'Dec'),
]
class PlantForm(forms.ModelForm):
bloom_months = forms.MultipleChoiceField(
choices=MONTH_CHOICES,
widget=forms.CheckboxSelectMultiple,
required=False,
label='Bloom months',
)
location = forms.ModelChoiceField(
queryset=Location.objects.all(),
required=False,
empty_label='— select location —',
widget=forms.Select(attrs={'class': 'form-select'}),
)
new_location = forms.CharField(
max_length=200,
required=False,
label='Or add new location',
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'e.g. Living room'}),
)
class Meta:
model = Plant
fields = ['name', 'location', 'is_indoor', 'bloom_months', 'notes']
widgets = {
'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'name': forms.TextInput(attrs={'class': 'form-control'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk and self.instance.bloom_months:
self.initial['bloom_months'] = [str(m) for m in self.instance.bloom_months]
elif not self.instance.pk and self.initial.get('bloom_months'):
self.initial['bloom_months'] = [str(m) for m in self.initial['bloom_months']]
def clean_bloom_months(self):
return [int(m) for m in self.cleaned_data.get('bloom_months', [])]
def clean(self):
cleaned = super().clean()
new_loc = cleaned.get('new_location', '').strip()
if new_loc:
loc, _ = Location.objects.get_or_create(name=new_loc)
cleaned['location'] = loc
return cleaned
class PruningLogForm(forms.ModelForm):
class Meta:
model = PruningLog
fields = ['pruned_on', 'notes']
widgets = {
'pruned_on': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'notes': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
}