import uuid from django.shortcuts import render, redirect from django.core.files.base import ContentFile from django.core.files.storage import default_storage from plants.services import plantnet from plants.services.plantnet import PlantNetError from plants.services.vpc import search_species as vpc_search from plants.models import Plant, Species _SESSION_KEYS = [ 'identify_matches', 'identify_image_paths', 'identify_selected', 'identify_vpc_results', 'identify_vpc_idx', ] _VALID_ORGANS = {'flower', 'leaf', 'fruit', 'bark', 'habit', 'auto'} def _clear_session(request): for key in _SESSION_KEYS: request.session.pop(key, None) def identify_upload(request): if request.method != 'POST': return render(request, 'plants/identify_upload.html') files = request.FILES.getlist('images') organs = request.POST.getlist('organs') if not files: return render(request, 'plants/identify_upload.html', {'error': 'Please select at least one photo.'}) scan_id = uuid.uuid4().hex saved_paths = [] images = [] for i, f in enumerate(files[:5]): organ = organs[i] if i < len(organs) else 'auto' if organ not in _VALID_ORGANS: organ = 'auto' file_bytes = f.read() name = f'plants/temp_identify/{scan_id}_{i}.jpg' default_storage.save(name, ContentFile(file_bytes)) saved_paths.append({'path': name, 'organ': organ}) images.append((file_bytes, organ)) try: matches = plantnet.identify(images) except PlantNetError as e: for p in saved_paths: if default_storage.exists(p['path']): default_storage.delete(p['path']) return render(request, 'plants/identify_upload.html', {'error': str(e)}) request.session['identify_matches'] = matches request.session['identify_image_paths'] = saved_paths return redirect('identify_confirm') def identify_confirm(request): if 'identify_matches' not in request.session: return redirect('identify_upload') matches = request.session['identify_matches'] if request.method == 'POST': try: idx = int(request.POST.get('match_idx', 0)) idx = max(0, min(idx, len(matches) - 1)) except (ValueError, TypeError): idx = 0 selected = matches[idx] request.session['identify_selected'] = selected try: vpc_results = vpc_search(selected['scientific_name'])[:3] except Exception: vpc_results = [] request.session['identify_vpc_results'] = vpc_results request.session['identify_vpc_idx'] = 0 return redirect('identify_fields') return render(request, 'plants/identify_confirm.html', {'matches': matches}) def _apply_vpc_care_fields(species, vpc_result): from plants.services.vpc import scrape_plant, VPCError slug = vpc_result.get('slug', '') if not slug: return try: data = scrape_plant(slug) except VPCError: return for field in ('bloom_months', 'max_height_cm', 'sunlight', 'growth_rate', 'description', 'api_image_url', 'frost_hardiness_c', 'planting_density_m2'): val = data.get(field) if val not in (None, '', []): setattr(species, field, val) species.vpc_slug = slug species.vpc_raw = data species.save() def identify_fields(request): if 'identify_selected' not in request.session: return redirect('identify_upload') selected = request.session['identify_selected'] vpc_results = request.session.get('identify_vpc_results', []) vpc_idx = request.session.get('identify_vpc_idx', 0) if request.method == 'GET' and 'vpc_idx' in request.GET: try: new_idx = int(request.GET['vpc_idx']) vpc_idx = max(0, min(new_idx, len(vpc_results) - 1)) request.session['identify_vpc_idx'] = vpc_idx except (ValueError, TypeError): pass return redirect('identify_fields') vpc_match = vpc_results[vpc_idx] if vpc_results else None if request.method == 'POST': common_name_source = request.POST.get('common_name_source', 'plantnet') scientific_name_source = request.POST.get('scientific_name_source', 'plantnet') name = request.POST.get('name', '').strip() or selected['scientific_name'] if common_name_source == 'vpc' and vpc_match: common_name = vpc_match['common_name'] else: common_name = (selected['common_names'] or [selected['scientific_name']])[0] if scientific_name_source == 'vpc' and vpc_match: scientific_name = vpc_match['scientific_name'] else: scientific_name = selected['scientific_name'] species = Species.objects.filter(scientific_name=scientific_name).first() if not species: species = Species.objects.create( scientific_name=scientific_name, common_name=common_name, ) else: species.common_name = common_name species.save(update_fields=['common_name']) if vpc_match: _apply_vpc_care_fields(species, vpc_match) plant = Plant.objects.create(name=name, species=species) try: for p in request.session.get('identify_image_paths', []): if default_storage.exists(p['path']): default_storage.delete(p['path']) finally: _clear_session(request) return redirect('plant_detail', pk=plant.pk) return render(request, 'plants/identify_fields.html', { 'selected': selected, 'vpc_results': vpc_results, 'vpc_idx': vpc_idx, 'vpc_match': vpc_match, })