feat: add plant form with species pre-fill
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
88976bb351
commit
3bc27a345f
3 changed files with 129 additions and 1 deletions
70
plants/templates/plants/plant_form.html
Normal file
70
plants/templates/plants/plant_form.html
Normal 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 %}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -59,8 +59,33 @@ def log_pruning(request, pk):
|
|||
|
||||
|
||||
def plant_add(request):
|
||||
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):
|
||||
pass
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue