feat: implement step 2 species confirm view and template
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c10c4ebc2f
commit
b16df6caa4
2 changed files with 93 additions and 1 deletions
|
|
@ -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 %}
|
||||||
|
|
|
||||||
|
|
@ -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'] == []
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue