From 7fef359ae0c340996bcb067a416b2b144d388913 Mon Sep 17 00:00:00 2001 From: Stephan Kerkman Date: Thu, 28 May 2026 18:49:15 +0200 Subject: [PATCH] feat: store VPC raw scrape data and add reset button Species.vpc_raw (JSONField) stores the full scrape payload at import time. vpc_species_reset view restores bloom_months, sunlight, max_height_cm etc. from that snapshot. If vpc_raw is missing (species imported before this feature), it re-scrapes from VPC on demand and saves the result. Reset button appears on plant detail page for any plant with a VPC species. A 'next' hidden field returns the user to the same plant after reset. Co-Authored-By: Claude Sonnet 4.6 --- plants/migrations/0006_species_vpc_raw.py | 16 +++++++++++ plants/models.py | 1 + plants/templates/plants/plant_detail.html | 12 +++++++++ plants/urls.py | 1 + plants/views/species.py | 33 ++++++++++++++++++++++- 5 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 plants/migrations/0006_species_vpc_raw.py diff --git a/plants/migrations/0006_species_vpc_raw.py b/plants/migrations/0006_species_vpc_raw.py new file mode 100644 index 0000000..cc23a0e --- /dev/null +++ b/plants/migrations/0006_species_vpc_raw.py @@ -0,0 +1,16 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('plants', '0005_species_vpc_slug'), + ] + + operations = [ + migrations.AddField( + model_name='species', + name='vpc_raw', + field=models.JSONField(blank=True, null=True), + ), + ] diff --git a/plants/models.py b/plants/models.py index 3f7ade9..27feab4 100644 --- a/plants/models.py +++ b/plants/models.py @@ -26,6 +26,7 @@ class Species(models.Model): api_image_url = models.URLField(blank=True) perenual_id = models.IntegerField(unique=True, null=True, blank=True) vpc_slug = models.CharField(max_length=200, blank=True, db_index=True) + vpc_raw = models.JSONField(null=True, blank=True) class Meta: ordering = ['common_name'] diff --git a/plants/templates/plants/plant_detail.html b/plants/templates/plants/plant_detail.html index ded196c..21ceb04 100644 --- a/plants/templates/plants/plant_detail.html +++ b/plants/templates/plants/plant_detail.html @@ -43,6 +43,18 @@ 🌱 {{ plant.species.growth_rate }} {% endif %} +{% if plant.species.vpc_slug %} +
+
+ {% csrf_token %} + + +
+
+{% endif %} {% endif %} {% load plant_extras %} diff --git a/plants/urls.py b/plants/urls.py index 79ea8df..75a8cab 100644 --- a/plants/urls.py +++ b/plants/urls.py @@ -16,4 +16,5 @@ urlpatterns = [ path('species/search/', species.species_search, name='species_search'), path('species/select/', species.species_select, name='species_select'), path('species/select/vpc/', species.vpc_select, name='vpc_select'), + path('species//reset-vpc/', species.vpc_species_reset, name='vpc_species_reset'), ] diff --git a/plants/views/species.py b/plants/views/species.py index 6af3e66..26b7a24 100644 --- a/plants/views/species.py +++ b/plants/views/species.py @@ -1,6 +1,7 @@ from concurrent.futures import ThreadPoolExecutor -from django.shortcuts import render, redirect +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 @@ -93,5 +94,35 @@ def vpc_select(request): 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)