feat: implement step 2 species confirm view and template

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Stephan Kerkman 2026-05-30 17:45:35 +02:00
parent c10c4ebc2f
commit b16df6caa4
2 changed files with 93 additions and 1 deletions

View file

@ -1,5 +1,54 @@
{% extends "plants/base.html" %} {% extends "plants/base.html" %}
{% block title %}Confirm species — PlantDB{% endblock %} {% block title %}Confirm species — PlantDB{% endblock %}
{% block content %} {% block content %}
<p>Confirm step (coming soon)</p>
<div class="d-flex align-items-center gap-2 mb-3">
<a href="{% url 'identify_upload' %}" class="btn btn-sm btn-outline-secondary"></a>
<h5 class="mb-0">Is this your plant?</h5>
</div>
<p class="text-muted small mb-3">Tap the correct species, then confirm.</p>
<form method="post" id="confirm-form">
{% csrf_token %}
<input type="hidden" name="match_idx" id="match_idx_input" value="0">
<div class="d-flex flex-column gap-2 mb-4" id="match-list">
{% for m in matches %}
<div class="match-card d-flex align-items-center gap-3 p-3 border rounded"
style="cursor:pointer; {% if forloop.first %}border-color:#52b788 !important; background:#f0faf4;{% endif %}"
data-idx="{{ forloop.counter0 }}"
onclick="selectMatch(this)">
<div class="flex-grow-1">
<div class="fw-bold">{{ m.scientific_name }}</div>
{% if m.common_names %}
<div class="text-muted small">{{ m.common_names|join:", " }}</div>
{% endif %}
<div class="text-muted small fst-italic">{{ m.family }}</div>
</div>
<div>
<span class="badge {% if m.score >= 0.70 %}bg-success{% elif m.score >= 0.30 %}bg-warning text-dark{% else %}bg-secondary{% endif %}">
{% widthratio m.score 1 100 %}%
</span>
</div>
</div>
{% endfor %}
</div>
<button type="submit" class="btn btn-success w-100" id="confirm-btn">
Use selected species →
</button>
</form>
<script>
function selectMatch(el) {
document.querySelectorAll('.match-card').forEach(c => {
c.style.borderColor = '';
c.style.background = '';
});
el.style.borderColor = '#52b788';
el.style.background = '#f0faf4';
document.getElementById('match_idx_input').value = el.dataset.idx;
}
</script>
{% endblock %} {% endblock %}

View file

@ -73,3 +73,46 @@ def test_upload_post_plantnet_error_rerenders(client, settings):
}) })
assert resp.status_code == 200 assert resp.status_code == 200
assert b'No match found' in resp.content assert b'No match found' in resp.content
def _set_matches(client):
session = client.session
session['identify_matches'] = [
{'scientific_name': 'Liatris spicata', 'common_names': ['Blazing star'],
'score': 0.94, 'gbif_id': '123', 'family': 'Asteraceae', 'genus': 'Liatris'},
{'scientific_name': 'Liatris pycnostachya', 'common_names': ['Prairie blazing star'],
'score': 0.04, 'gbif_id': '456', 'family': 'Asteraceae', 'genus': 'Liatris'},
]
session['identify_image_paths'] = [{'path': 'plants/temp_identify/x_0.jpg', 'organ': 'flower'}]
session.save()
@pytest.mark.django_db
def test_confirm_get_with_session(client):
_set_matches(client)
resp = client.get(reverse('identify_confirm'))
assert resp.status_code == 200
assert b'Liatris spicata' in resp.content
@pytest.mark.django_db
def test_confirm_post_stores_selection_and_redirects(client):
_set_matches(client)
with patch('plants.views.identify.vpc_search', return_value=[
{'slug': 'liatris-spicata', 'common_name': 'Liatris spicata', 'scientific_name': 'Liatris spicata', 'thumbnail_url': '', 'source': 'vpc'},
]):
resp = client.post(reverse('identify_confirm'), {'match_idx': '0'})
assert resp.status_code == 302
assert reverse('identify_fields') in resp['Location']
assert client.session['identify_selected']['scientific_name'] == 'Liatris spicata'
assert len(client.session['identify_vpc_results']) == 1
@pytest.mark.django_db
def test_confirm_post_handles_vpc_failure(client):
_set_matches(client)
from plants.services.vpc import VPCError
with patch('plants.views.identify.vpc_search', side_effect=VPCError('down')):
resp = client.post(reverse('identify_confirm'), {'match_idx': '0'})
assert resp.status_code == 302
assert client.session['identify_vpc_results'] == []