from concurrent.futures import ThreadPoolExecutor from django.shortcuts import render, redirect, get_object_or_404 from django.urls import reverse from django.views.decorators.http import require_POST from plants.models import Species from plants.services.perenual import search_species as perenual_search, PerenualError from plants.services import vpc as vpc_service from plants.services.vpc import VPCError _MONTH_MAP = { 'January': 1, 'February': 2, 'March': 3, 'April': 4, 'May': 5, 'June': 6, 'July': 7, 'August': 8, 'September': 9, 'October': 10, 'November': 11, 'December': 12, } def species_search(request): q = request.GET.get('q', '').strip() perenual_results = [] vpc_results = [] error = None if len(q) >= 2: # If the query looks like a VPC URL or slug, skip the search and show # it directly as a single importable result. vpc_slug = vpc_service.slug_from_query(q) if vpc_slug: vpc_results = [{'slug': vpc_slug, 'common_name': vpc_slug.replace('-', ' ').title(), 'scientific_name': '', 'thumbnail_url': '', 'source': 'vpc'}] else: def _run_perenual(): try: return perenual_search(q), None except PerenualError as e: return [], str(e) def _run_vpc(): try: return vpc_service.search_species(q) except VPCError: return [] with ThreadPoolExecutor(max_workers=2) as ex: f_p = ex.submit(_run_perenual) f_v = ex.submit(_run_vpc) perenual_results, error = f_p.result() vpc_results = f_v.result() return render(request, 'plants/partials/species_results.html', { 'perenual_results': perenual_results, 'vpc_results': vpc_results, 'q': q, 'error': error, }) def species_select(request): perenual_id = int(request.POST['perenual_id']) if not Species.objects.filter(perenual_id=perenual_id).exists(): pruning_str = request.POST.get('pruning_months', '') pruning_months = [ _MONTH_MAP[m.strip()] for m in pruning_str.split(',') if m.strip() in _MONTH_MAP ] Species.objects.create( perenual_id=perenual_id, common_name=request.POST.get('common_name', ''), scientific_name=request.POST.get('scientific_name', ''), watering=request.POST.get('watering', ''), sunlight=request.POST.get('sunlight', ''), pruning_months=pruning_months, api_image_url=request.POST.get('api_image_url', ''), ) species = Species.objects.get(perenual_id=perenual_id) return redirect(f"{reverse('plant_add')}?species_id={species.pk}") def vpc_select(request): slug = request.POST.get('vpc_slug', '').strip() if not slug: return redirect(reverse('plant_add')) existing = Species.objects.filter(vpc_slug=slug).first() if existing: return redirect(f"{reverse('plant_add')}?species_id={existing.pk}") try: data = vpc_service.scrape_plant(slug) except VPCError: # Fall back to plant_add without species pre-fill return redirect(f"{reverse('plant_add')}?species_id=skip") species = Species.objects.create( common_name=data['common_name'], scientific_name=data['scientific_name'], bloom_months=data['bloom_months'], max_height_cm=data['max_height_cm'], sunlight=data['sunlight'], growth_rate=data['growth_rate'], description=data['description'], api_image_url=data['api_image_url'], vpc_slug=slug, vpc_raw=data, ) return redirect(f"{reverse('plant_add')}?species_id={species.pk}") @require_POST def vpc_species_reset(request, species_pk): """Restore species fields to the original VPC scrape values.""" species = get_object_or_404(Species, pk=species_pk, vpc_slug__gt='') raw = species.vpc_raw if not raw: # vpc_raw missing (imported before this feature) — re-scrape try: raw = vpc_service.scrape_plant(species.vpc_slug) species.vpc_raw = raw except VPCError: pass if raw: species.common_name = raw.get('common_name', species.common_name) species.scientific_name = raw.get('scientific_name', species.scientific_name) species.bloom_months = raw.get('bloom_months', species.bloom_months) species.max_height_cm = raw.get('max_height_cm', species.max_height_cm) species.sunlight = raw.get('sunlight', species.sunlight) species.growth_rate = raw.get('growth_rate', species.growth_rate) species.description = raw.get('description', species.description) species.api_image_url = raw.get('api_image_url', species.api_image_url) species.save() # Redirect back to the plant that triggered the reset, if provided next_url = request.POST.get('next') or reverse('plant_list') return redirect(next_url)