feat: add plant form with species pre-fill

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Stephan Kerkman 2026-05-28 10:05:18 +02:00
parent 88976bb351
commit 3bc27a345f
3 changed files with 129 additions and 1 deletions

View file

@ -0,0 +1,70 @@
{% extends "plants/base.html" %}
{% block title %}{{ title }} — PlantDB{% endblock %}
{% block content %}
<div class="d-flex align-items-center gap-2 mb-3">
<a href="{% url 'plant_list' %}" class="btn btn-sm btn-outline-secondary"></a>
<h5 class="mb-0">{{ title }}</h5>
</div>
{% if species %}
<div class="alert alert-success py-2 d-flex align-items-center gap-2">
{% if species.api_image_url %}
<img src="{{ species.api_image_url }}" width="36" height="36" class="rounded" style="object-fit:cover;">
{% endif %}
<div>
<strong>{{ species.common_name }}</strong>
{% if species.scientific_name %}<small class="text-muted fst-italic ms-1">{{ species.scientific_name }}</small>{% endif %}
<div class="small">Care info pre-filled from species database.</div>
</div>
</div>
{% endif %}
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<input type="hidden" name="species_id" value="{{ species_id|default:'' }}">
<div class="mb-3">
<label class="form-label fw-semibold">Your name for this plant *</label>
{{ form.name }}
{% if form.name.errors %}<div class="text-danger small">{{ form.name.errors }}</div>{% endif %}
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Location</label>
{{ form.location }}
{% if form.location.errors %}<div class="text-danger small">{{ form.location.errors }}</div>{% endif %}
</div>
<div class="mb-3 form-check">
{{ form.is_indoor }}
<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">Pruning months</label>
<div class="month-checkboxes d-flex flex-wrap gap-1">
{% for widget in form.pruning_months %}
<div class="form-check form-check-inline border rounded px-2 py-1">
{{ widget.tag }}
<label class="form-check-label" for="{{ widget.id_for_label }}">{{ widget.choice_label }}</label>
</div>
{% endfor %}
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Photo</label>
{{ form.photo }}
<div class="form-text">Take a photo with your camera or upload one.</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Notes</label>
{{ form.notes }}
</div>
<div class="d-grid">
<button type="submit" class="btn btn-success">Save plant</button>
</div>
</form>
{% endblock %}

View file

@ -149,3 +149,36 @@ class TestSpeciesSearch:
'api_image_url': '',
})
assert Species.objects.filter(perenual_id=99).count() == 1
@pytest.mark.django_db
class TestPlantAdd:
def test_add_with_species_shows_prefilled_form(self, client):
from plants.models import Species
s = Species.objects.create(common_name='Rose', pruning_months=[2, 8])
resp = client.get(reverse('plant_add') + f'?species_id={s.pk}')
assert resp.status_code == 200
assert resp.context['species'] == s
def test_post_creates_plant_and_redirects(self, client):
resp = client.post(reverse('plant_add'), {
'name': 'New Fern',
'location': 'Office',
'is_indoor': 'on',
'notes': '',
'pruning_months': [],
})
assert Plant.objects.filter(name='New Fern').exists()
plant = Plant.objects.get(name='New Fern')
assert resp.status_code == 302
assert resp['Location'] == f'/plants/{plant.pk}/'
def test_post_with_species_links_species(self, client):
from plants.models import Species
s = Species.objects.create(common_name='Rose', pruning_months=[2])
client.post(
reverse('plant_add') + f'?species_id={s.pk}',
{'name': 'Garden Rose', 'location': 'Garden', 'notes': '', 'pruning_months': ['2']},
)
plant = Plant.objects.get(name='Garden Rose')
assert plant.species == s

View file

@ -59,7 +59,32 @@ def log_pruning(request, pk):
def plant_add(request):
return render(request, 'plants/species_search.html')
species_id = request.GET.get('species_id') or request.POST.get('species_id')
species = None
if species_id and species_id != 'skip':
species = get_object_or_404(Species, pk=species_id)
if species_id is None and request.method == 'GET':
return render(request, 'plants/species_search.html')
if request.method == 'POST':
form = PlantForm(request.POST, request.FILES)
if form.is_valid():
plant = form.save(commit=False)
plant.species = species
plant.save()
return redirect('plant_detail', pk=plant.pk)
else:
initial = {'pruning_months': species.pruning_months} if species else {}
form = PlantForm(initial=initial)
return render(request, 'plants/plant_form.html', {
'form': form,
'species': species,
'species_id': species_id,
'title': 'Add Plant',
})
def plant_edit(request, pk):